├── resources └── icons │ ├── run.png │ ├── stop.png │ ├── faster.png │ ├── pause.png │ ├── resume.png │ ├── slower.png │ └── sub_menu_arrow.png ├── screenshots ├── menubar.gif └── picture.gif ├── main.cpp ├── interactive_buttons ├── winmaxbutton.h ├── winminbutton.h ├── winrestorebutton.h ├── winmenubutton.h ├── infobutton.h ├── pointmenubutton.h ├── winclosebutton.h ├── appendbutton.h ├── winsidebarbutton.h ├── waterzoombutton.h ├── winmaxbutton.cpp ├── waterfloatbutton.h ├── winminbutton.cpp ├── watercirclebutton.h ├── generalbuttoninterface.cpp ├── threedimenbutton.h ├── generalbuttoninterface.h ├── appendbutton.cpp ├── waterzoombutton.cpp ├── waterfallbuttongroup.h ├── winclosebutton.cpp ├── winsidebarbutton.cpp ├── winrestorebutton.cpp ├── watercirclebutton.cpp ├── pointmenubutton.cpp ├── winmenubutton.cpp ├── infobutton.cpp ├── threedimenbutton.cpp ├── waterfloatbutton.cpp ├── waterfallbuttongroup.cpp └── interactivebuttonbase.h ├── facile_menu ├── facilemenubarinterface.h ├── facilemenubar.h ├── facilemenuanimation.h ├── color_list.txt ├── facilemenuitem.h ├── facilemenubar.cpp ├── facilemenu.h └── facilemenuitem.cpp ├── resources.qrc ├── mainwindow.h ├── .gitignore ├── color_octree ├── coloroctree.h ├── imageutil.h ├── coloroctree.cpp └── imageutil.cpp ├── FacileMenu.pro ├── mainwindow.ui ├── LICENSE ├── mainwindow.cpp └── README.md /resources/icons/run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-FacileMenu/HEAD/resources/icons/run.png -------------------------------------------------------------------------------- /resources/icons/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-FacileMenu/HEAD/resources/icons/stop.png -------------------------------------------------------------------------------- /screenshots/menubar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-FacileMenu/HEAD/screenshots/menubar.gif -------------------------------------------------------------------------------- /screenshots/picture.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-FacileMenu/HEAD/screenshots/picture.gif -------------------------------------------------------------------------------- /resources/icons/faster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-FacileMenu/HEAD/resources/icons/faster.png -------------------------------------------------------------------------------- /resources/icons/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-FacileMenu/HEAD/resources/icons/pause.png -------------------------------------------------------------------------------- /resources/icons/resume.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-FacileMenu/HEAD/resources/icons/resume.png -------------------------------------------------------------------------------- /resources/icons/slower.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-FacileMenu/HEAD/resources/icons/slower.png -------------------------------------------------------------------------------- /resources/icons/sub_menu_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/Qt-FacileMenu/HEAD/resources/icons/sub_menu_arrow.png -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QApplication a(argc, argv); 8 | // Mac窗口 9 | qputenv("QT_MAC_WANTS_LAYER", "1"); 10 | 11 | FacileMenu::auto_theme_by_bg = true; 12 | MainWindow w; 13 | w.show(); 14 | return a.exec(); 15 | } 16 | -------------------------------------------------------------------------------- /interactive_buttons/winmaxbutton.h: -------------------------------------------------------------------------------- 1 | #ifndef WINMAXBUTTON_H 2 | #define WINMAXBUTTON_H 3 | 4 | #include "interactivebuttonbase.h" 5 | 6 | class WinMaxButton : public InteractiveButtonBase 7 | { 8 | public: 9 | WinMaxButton(QWidget* parent = nullptr); 10 | 11 | protected: 12 | void paintEvent(QPaintEvent*event); 13 | }; 14 | 15 | #endif // WINMAXBUTTON_H 16 | -------------------------------------------------------------------------------- /interactive_buttons/winminbutton.h: -------------------------------------------------------------------------------- 1 | #ifndef WINMINBUTTON_H 2 | #define WINMINBUTTON_H 3 | 4 | #include "interactivebuttonbase.h" 5 | 6 | class WinMinButton : public InteractiveButtonBase 7 | { 8 | Q_OBJECT 9 | public: 10 | WinMinButton(QWidget* parent = nullptr); 11 | 12 | void paintEvent(QPaintEvent* event) override; 13 | }; 14 | 15 | #endif // WINMINBUTTON_H -------------------------------------------------------------------------------- /interactive_buttons/winrestorebutton.h: -------------------------------------------------------------------------------- 1 | #ifndef WINRESTOREBUTTON_H 2 | #define WINRESTOREBUTTON_H 3 | 4 | #include "interactivebuttonbase.h" 5 | 6 | class WinRestoreButton : public InteractiveButtonBase 7 | { 8 | public: 9 | WinRestoreButton(QWidget* parent = nullptr); 10 | 11 | void paintEvent(QPaintEvent* event) override; 12 | }; 13 | 14 | #endif // WINRESTOREBUTTON_H 15 | -------------------------------------------------------------------------------- /interactive_buttons/winmenubutton.h: -------------------------------------------------------------------------------- 1 | #ifndef WINMENUBUTTON_H 2 | #define WINMENUBUTTON_H 3 | 4 | #include "interactivebuttonbase.h" 5 | 6 | class WinMenuButton : public InteractiveButtonBase 7 | { 8 | public: 9 | WinMenuButton(QWidget* parent = nullptr); 10 | 11 | protected: 12 | void paintEvent(QPaintEvent*event); 13 | void slotClicked(); 14 | }; 15 | 16 | #endif // WINMENUBUTTON_H 17 | -------------------------------------------------------------------------------- /facile_menu/facilemenubarinterface.h: -------------------------------------------------------------------------------- 1 | #ifndef FACILEMENUBARINTERFACE_H 2 | #define FACILEMENUBARINTERFACE_H 3 | 4 | #include 5 | 6 | class FacileMenuBarInterface 7 | { 8 | public: 9 | virtual void trigger(int) = 0; 10 | virtual bool triggerIfNot(int, void*) = 0; 11 | virtual int isCursorInArea(QPoint) const = 0; 12 | virtual int currentIndex() const = 0; 13 | }; 14 | 15 | #endif // FACILEMENUBARINTERFACE_H 16 | -------------------------------------------------------------------------------- /interactive_buttons/infobutton.h: -------------------------------------------------------------------------------- 1 | #ifndef INFOBUTTON_H 2 | #define INFOBUTTON_H 3 | 4 | #include "interactivebuttonbase.h" 5 | 6 | class InfoButton : public InteractiveButtonBase 7 | { 8 | public: 9 | InfoButton(QWidget* parent = nullptr); 10 | 11 | protected: 12 | void paintEvent(QPaintEvent *event) override; 13 | void mousePressEvent(QMouseEvent *event) override; 14 | 15 | private: 16 | bool tongue = false; 17 | }; 18 | 19 | #endif // INFOBUTTON_H 20 | -------------------------------------------------------------------------------- /interactive_buttons/pointmenubutton.h: -------------------------------------------------------------------------------- 1 | #ifndef POINTMENUBUTTON_H 2 | #define POINTMENUBUTTON_H 3 | 4 | #include 5 | #include "interactivebuttonbase.h" 6 | 7 | #define ANI_STEP_3 40 8 | 9 | class PointMenuButton : public InteractiveButtonBase 10 | { 11 | public: 12 | PointMenuButton(QWidget* parent = nullptr); 13 | 14 | protected: 15 | void mousePressEvent(QMouseEvent *event) override; 16 | void paintEvent(QPaintEvent*event) override; 17 | 18 | private: 19 | int radius; 20 | }; 21 | 22 | #endif // POINTMENUBUTTON_H 23 | -------------------------------------------------------------------------------- /interactive_buttons/winclosebutton.h: -------------------------------------------------------------------------------- 1 | #ifndef WINCLOSEBUTTON_H 2 | #define WINCLOSEBUTTON_H 3 | 4 | #include "interactivebuttonbase.h" 5 | 6 | #include 7 | 8 | class WinCloseButton : public InteractiveButtonBase 9 | { 10 | public: 11 | WinCloseButton(QWidget* parent = nullptr); 12 | 13 | void setTopRightRadius(int r); 14 | 15 | protected: 16 | void paintEvent(QPaintEvent*event); 17 | 18 | QPainterPath getBgPainterPath(); 19 | QPainterPath getWaterPainterPath(Water water); 20 | 21 | private: 22 | int tr_radius; 23 | }; 24 | 25 | #endif // WINCLOSEBUTTON_H 26 | -------------------------------------------------------------------------------- /interactive_buttons/appendbutton.h: -------------------------------------------------------------------------------- 1 | #ifndef APPENDBUTTON_H 2 | #define APPENDBUTTON_H 3 | 4 | #include "interactivebuttonbase.h" 5 | 6 | class AppendButton : public InteractiveButtonBase 7 | { 8 | public: 9 | AppendButton(QWidget *parent = nullptr); 10 | 11 | protected: 12 | void paintEvent(QPaintEvent *event) override; 13 | void enterEvent(QEvent *event) override; 14 | void mouseReleaseEvent(QMouseEvent *event) override; 15 | 16 | public slots: 17 | virtual void anchorTimeOut() override; 18 | 19 | private: 20 | int add_angle = 0; // 旋转角度 21 | int rotate_speed = 2; // 旋转的速度 22 | }; 23 | 24 | #endif // APPENDBUTTON_H 25 | -------------------------------------------------------------------------------- /interactive_buttons/winsidebarbutton.h: -------------------------------------------------------------------------------- 1 | #ifndef WINSIDEBARBUTTON_H 2 | #define WINSIDEBARBUTTON_H 3 | 4 | #include 5 | #include 6 | #include "interactivebuttonbase.h" 7 | 8 | class WinSidebarButton : public InteractiveButtonBase 9 | { 10 | public: 11 | WinSidebarButton(QWidget *parent = nullptr); 12 | 13 | void setTopLeftRadius(int r); 14 | 15 | protected: 16 | void paintEvent(QPaintEvent*event); 17 | void slotClicked(); 18 | 19 | QPainterPath getBgPainterPath(); 20 | QPainterPath getWaterPainterPath(Water water); 21 | 22 | private: 23 | int tl_radius; 24 | }; 25 | 26 | #endif // WINSIDEBARBUTTON_H 27 | -------------------------------------------------------------------------------- /resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | resources/icons/faster.png 4 | resources/icons/pause.png 5 | resources/icons/resume.png 6 | resources/icons/run.png 7 | resources/icons/slower.png 8 | resources/icons/stop.png 9 | resources/icons/sub_menu_arrow.png 10 | 11 | 12 | facile_menu/color_list.txt 13 | 14 | 15 | -------------------------------------------------------------------------------- /mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include "facilemenu.h" 7 | 8 | QT_BEGIN_NAMESPACE 9 | namespace Ui { class MainWindow; } 10 | QT_END_NAMESPACE 11 | 12 | class MainWindow : public QMainWindow 13 | { 14 | Q_OBJECT 15 | public: 16 | MainWindow(QWidget *parent = nullptr); 17 | ~MainWindow(); 18 | 19 | public slots: 20 | void on_pushButton_clicked(); 21 | 22 | void on_pushButton_2_clicked(); 23 | 24 | static void staticFunction(); 25 | void classFunction(); 26 | 27 | private slots: 28 | void on_pushButton_3_clicked(); 29 | 30 | private: 31 | Ui::MainWindow *ui; 32 | }; 33 | #endif // MAINWINDOW_H 34 | -------------------------------------------------------------------------------- /interactive_buttons/waterzoombutton.h: -------------------------------------------------------------------------------- 1 | #ifndef WATERZOOMBUTTON_H 2 | #define WATERZOOMBUTTON_H 3 | 4 | #include "interactivebuttonbase.h" 5 | 6 | const int DEFAULT_CHOKING = 5; 7 | 8 | class WaterZoomButton : public InteractiveButtonBase 9 | { 10 | public: 11 | WaterZoomButton(QString text, QWidget* parent = nullptr); 12 | WaterZoomButton(QWidget* parent = nullptr); 13 | 14 | void setChoking(int c); 15 | void setChokingProp(double p); 16 | void setRadiusZoom(int radius); 17 | void setRadius(int x, int x2); 18 | 19 | int getChokingSpacing(); 20 | static int getDefaultSpacing(); 21 | 22 | protected: 23 | QPainterPath getBgPainterPath() override; 24 | void resizeEvent(QResizeEvent *event) override; 25 | 26 | protected: 27 | int choking; 28 | double choking_prop; 29 | int radius_zoom; 30 | }; 31 | 32 | #endif // WATERZOOMBUTTON_H 33 | -------------------------------------------------------------------------------- /interactive_buttons/winmaxbutton.cpp: -------------------------------------------------------------------------------- 1 | #include "winmaxbutton.h" 2 | 3 | WinMaxButton::WinMaxButton(QWidget *parent) 4 | : InteractiveButtonBase (parent) 5 | { 6 | setUnifyGeomerey(true); 7 | } 8 | 9 | void WinMaxButton::paintEvent(QPaintEvent *event) 10 | { 11 | InteractiveButtonBase::paintEvent(event); 12 | 13 | if (!show_foreground) return ; // 不显示前景 14 | 15 | int w = _w, h = _h; 16 | int dx = offset_pos.x(), dy = offset_pos.y(); 17 | QRect r; 18 | if (click_ani_appearing || click_ani_disappearing) 19 | { 20 | double pro = click_ani_progress / 800.0; 21 | r = QRect( 22 | _l+(w/3+dx) - (w/3+dx)*pro, 23 | _t+(h/3+dy) - (h/3+dy)*pro, 24 | w/3 + (w*2/3)*pro, 25 | h/3 + (h*2/3)*pro 26 | ); 27 | } 28 | else 29 | { 30 | r = QRect(_l+w/3+dx, _t+h/3+dy, w/3, h/3); 31 | } 32 | 33 | 34 | QPainter painter(this); 35 | painter.setPen(QPen(icon_color)); 36 | painter.drawRect(r); 37 | } 38 | -------------------------------------------------------------------------------- /interactive_buttons/waterfloatbutton.h: -------------------------------------------------------------------------------- 1 | #ifndef WATERFLOATBUTTON_H 2 | #define WATERFLOATBUTTON_H 3 | 4 | #include "interactivebuttonbase.h" 5 | 6 | class WaterFloatButton : public InteractiveButtonBase 7 | { 8 | public: 9 | WaterFloatButton(QWidget* parent = nullptr); 10 | WaterFloatButton(QString s, QWidget* parent = nullptr); 11 | 12 | protected: 13 | void enterEvent(QEvent* event) override; 14 | void leaveEvent(QEvent* event) override; 15 | void mousePressEvent(QMouseEvent* event) override; 16 | void mouseReleaseEvent(QMouseEvent* event) override; 17 | void mouseMoveEvent(QMouseEvent* event) override; 18 | void resizeEvent(QResizeEvent* event) override; 19 | void paintEvent(QPaintEvent *event) override; 20 | 21 | QPainterPath getBgPainterPath() override; 22 | QPainterPath getWaterPainterPath(Water water) override; 23 | 24 | bool inArea(QPoint point) override; 25 | 26 | protected: 27 | QPoint center_pos; 28 | bool in_area; 29 | int mwidth; 30 | int radius; 31 | }; 32 | 33 | #endif // WATERFLOATBUTTON_H 34 | -------------------------------------------------------------------------------- /interactive_buttons/winminbutton.cpp: -------------------------------------------------------------------------------- 1 | #include "winminbutton.h" 2 | 3 | WinMinButton::WinMinButton(QWidget* parent) 4 | : InteractiveButtonBase(parent) 5 | { 6 | setUnifyGeomerey(true); 7 | } 8 | 9 | void WinMinButton::paintEvent(QPaintEvent* event) 10 | { 11 | InteractiveButtonBase::paintEvent(event); 12 | 13 | if (!show_foreground) return ; // 不显示前景 14 | 15 | int w = _w, h = _h; 16 | QPoint left(_l+w/3, _t+h/2), right(_l+w*2/3, _t+h/2), 17 | mid(_l+w/2+offset_pos.x(), _t+h/2+offset_pos.y()); 18 | 19 | if (click_ani_appearing || click_ani_disappearing) 20 | { 21 | double pro = click_ani_progress / 800.0; 22 | left.setX(left.x()-left.x() * pro); 23 | right.setX(right.x()+(w-right.x()) * pro); 24 | } 25 | 26 | QPainter painter(this); 27 | QPainterPath path; 28 | path.moveTo(left); 29 | path.cubicTo(left, mid, right); 30 | painter.setPen(QPen(icon_color)); 31 | if (left.y() != mid.y()) 32 | painter.setRenderHint(QPainter::Antialiasing,true); 33 | painter.drawPath(path); 34 | } 35 | -------------------------------------------------------------------------------- /.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 | /build/ 74 | .vscode -------------------------------------------------------------------------------- /interactive_buttons/watercirclebutton.h: -------------------------------------------------------------------------------- 1 | #ifndef WATERCIRCLEBUTTON_H 2 | #define WATERCIRCLEBUTTON_H 3 | 4 | #include "interactivebuttonbase.h" 5 | 6 | class WaterCircleButton : public InteractiveButtonBase 7 | { 8 | public: 9 | WaterCircleButton(QWidget* parent = nullptr); 10 | WaterCircleButton(QIcon icon, QWidget* parent = nullptr); 11 | WaterCircleButton(QPixmap pixmap, QWidget* parent = nullptr); 12 | 13 | protected: 14 | void enterEvent(QEvent* event) override; 15 | void leaveEvent(QEvent* event) override; 16 | void mousePressEvent(QMouseEvent* event) override; 17 | void mouseReleaseEvent(QMouseEvent* event) override; 18 | void mouseMoveEvent(QMouseEvent* event) override; 19 | void resizeEvent(QResizeEvent* event) override; 20 | 21 | QPainterPath getBgPainterPath() override; 22 | QPainterPath getWaterPainterPath(Water water) override; 23 | 24 | void simulateStatePress(bool s = true); 25 | bool inArea(QPoint point) override; 26 | 27 | protected: 28 | QPoint center_pos; 29 | bool in_circle; 30 | int radius; 31 | }; 32 | 33 | #endif // WATERCIRCLEBUTTON_H 34 | -------------------------------------------------------------------------------- /interactive_buttons/generalbuttoninterface.cpp: -------------------------------------------------------------------------------- 1 | #include "generalbuttoninterface.h" 2 | 3 | GeneralButtonInterface::GeneralButtonInterface() : QPushButton () 4 | { 5 | init(); 6 | } 7 | 8 | GeneralButtonInterface::GeneralButtonInterface(QWidget *parent) : QPushButton (parent) 9 | { 10 | init(); 11 | } 12 | 13 | GeneralButtonInterface::GeneralButtonInterface(QString icon, QWidget *parent) : QPushButton (QIcon(icon), "", parent) 14 | { 15 | init(); 16 | } 17 | 18 | GeneralButtonInterface::GeneralButtonInterface(QIcon icon, QWidget *parent) : QPushButton (icon, "", parent) 19 | { 20 | init(); 21 | } 22 | 23 | GeneralButtonInterface::GeneralButtonInterface(QIcon icon, QString text, QWidget *parent) : QPushButton (icon, text, parent) 24 | { 25 | init(); 26 | } 27 | 28 | GeneralButtonInterface::GeneralButtonInterface(QString icon, QString text, QWidget *parent) : QPushButton (QIcon(icon), text, parent) 29 | { 30 | init(); 31 | } 32 | 33 | void GeneralButtonInterface::init() 34 | { 35 | static_fixed = true; 36 | store_direct = DIRECT_NONE; 37 | // connect(thm, SIGNAL(windowChanged()), this, SLOT(updateUI())); 38 | } 39 | 40 | void GeneralButtonInterface::setStore(DirectType type) 41 | { 42 | this->store_direct = type; 43 | } 44 | -------------------------------------------------------------------------------- /interactive_buttons/threedimenbutton.h: -------------------------------------------------------------------------------- 1 | #ifndef THREEDIMENBUTTON_H 2 | #define THREEDIMENBUTTON_H 3 | 4 | #include 5 | #include "interactivebuttonbase.h" 6 | 7 | class ThreeDimenButton : public InteractiveButtonBase 8 | { 9 | #define AOPER 10 10 | #define SHADE 10 11 | Q_OBJECT 12 | public: 13 | ThreeDimenButton(QWidget* parent = nullptr); 14 | 15 | protected: 16 | void enterEvent(QEvent* event) override; 17 | void leaveEvent(QEvent* event) override; 18 | void mousePressEvent(QMouseEvent* event) override; 19 | void mouseReleaseEvent(QMouseEvent* event) override; 20 | void mouseMoveEvent(QMouseEvent* event) override; 21 | void resizeEvent(QResizeEvent* event) override; 22 | 23 | void anchorTimeOut() override; 24 | 25 | QPainterPath getBgPainterPath() override; 26 | QPainterPath getWaterPainterPath(InteractiveButtonBase::Water water) override; 27 | 28 | void simulateStatePress(bool s = true, bool a = false) override; 29 | bool inArea(QPointF point) override; 30 | 31 | private: 32 | double cha_cheng(QPointF a, QPointF b); 33 | double dian_cheng(QPointF a, QPointF b); 34 | QPointF limitPointXY(QPointF v, int w, int h); 35 | 36 | protected: 37 | QGraphicsDropShadowEffect* shadow_effect; 38 | bool in_rect; 39 | int aop_w, aop_h; 40 | }; 41 | 42 | #endif // THREEDIMENBUTTON_H 43 | -------------------------------------------------------------------------------- /facile_menu/facilemenubar.h: -------------------------------------------------------------------------------- 1 | #ifndef FACILEMENUBAR_H 2 | #define FACILEMENUBAR_H 3 | 4 | #include 5 | #include 6 | #include "facilemenubarinterface.h" 7 | #include "facilemenu.h" 8 | 9 | class FacileSwitchWidget; 10 | 11 | class FacileMenuBar : public QWidget, public FacileMenuBarInterface 12 | { 13 | Q_OBJECT 14 | public: 15 | explicit FacileMenuBar(QWidget *parent = nullptr); 16 | 17 | virtual int isCursorInArea(QPoint pos) const override; 18 | int currentIndex() const override; 19 | virtual bool triggerIfNot(int index, void*menu) override; 20 | 21 | void addMenu(QString name, FacileMenu* menu); 22 | void insertMenu(int index, QString name, FacileMenu* menu); 23 | void deleteMenu(int index); 24 | int count() const; 25 | void setAnimationEnabled(bool en); // 开启动画,目前会有些问题 26 | 27 | signals: 28 | void triggered(); 29 | 30 | public slots: 31 | virtual void trigger(int index) override; 32 | virtual void switchTrigger(int index, int prevIndex); 33 | 34 | private: 35 | InteractiveButtonBase* createButton(QString name, FacileMenu* menu); 36 | 37 | private: 38 | QList buttons; 39 | QList menus; 40 | QHBoxLayout* hlayout; 41 | 42 | int _currentIndex = -1; 43 | bool enableAnimation = false; 44 | FacileSwitchWidget* aniWidget = nullptr; 45 | }; 46 | 47 | #endif // FACILEMENUBAR_H 48 | -------------------------------------------------------------------------------- /interactive_buttons/generalbuttoninterface.h: -------------------------------------------------------------------------------- 1 | #ifndef GENERALBUTTON_H 2 | #define GENERALBUTTON_H 3 | 4 | #include 5 | #include 6 | #include 7 | //#include "defines.h" 8 | //#include "globalvar.h" 9 | 10 | class GeneralButtonInterface : public QPushButton 11 | { 12 | Q_OBJECT 13 | public: 14 | enum DirectType { 15 | DIRECT_NONE, 16 | DIRECT_TOP, 17 | DIRECT_LEFT, 18 | DIRECT_RIGHT, 19 | DIRECT_BOTTOM 20 | }; 21 | 22 | GeneralButtonInterface(); 23 | GeneralButtonInterface(QWidget* parent); 24 | GeneralButtonInterface(QString icon, QWidget* parent); 25 | GeneralButtonInterface(QIcon icon, QWidget* parent); 26 | GeneralButtonInterface(QIcon icon, QString text, QWidget* parent); 27 | GeneralButtonInterface(QString icon, QString text, QWidget* parent); 28 | 29 | void init(); 30 | 31 | virtual void setStore(DirectType type); 32 | virtual void showFore(){} 33 | virtual void hideFore(){} 34 | virtual void showBack(){} 35 | virtual void hideBack(){} 36 | 37 | virtual void disableFixed(){ this->static_fixed=false; } 38 | virtual void setFixed(){this->static_fixed=true;} 39 | virtual bool isFixed(){return static_fixed;} 40 | 41 | public slots: 42 | virtual void updateUI(){} 43 | 44 | 45 | protected: 46 | bool static_fixed; 47 | DirectType store_direct; 48 | }; 49 | 50 | #endif // GENERALBUTTON_H 51 | -------------------------------------------------------------------------------- /color_octree/coloroctree.h: -------------------------------------------------------------------------------- 1 | #ifndef COLOROCTREE_H 2 | #define COLOROCTREE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | typedef QList ColorList; 10 | 11 | class ColorOctree 12 | { 13 | public: 14 | ColorOctree(); 15 | ColorOctree(QImage image, int maxPool = 250000, int maxCount = 8); 16 | ~ColorOctree(); 17 | 18 | struct RGB 19 | { 20 | int red, green, blue; 21 | }; 22 | 23 | struct OctreeNode 24 | { 25 | ~OctreeNode() 26 | { 27 | for (int i = 0; i < 8; i++) 28 | if (children[i]) 29 | delete children[i]; 30 | } 31 | 32 | long long red = 0, green = 0, blue = 0; // 许多点累加的结果,会非常大 33 | bool isLeaf = false; 34 | int pixelCount = 0; 35 | OctreeNode *children[8] = {}; 36 | }; 37 | 38 | struct ColorCount 39 | { 40 | int count = 0; 41 | char color[7] = {}; // 16进制字符串 42 | int red = 0, green = 0, blue = 0; 43 | int colorValue = 0; // 对应int值 44 | 45 | QColor toColor() const 46 | { 47 | return QColor(red, green, blue); 48 | } 49 | }; 50 | 51 | void buildTree(QImage image, int maxCount = 20); 52 | QList result(); 53 | 54 | private: 55 | void addColor(OctreeNode *node, RGB* color, int level); 56 | bool reduceTree(); 57 | void colorStats(OctreeNode *node, QList *colors); 58 | 59 | private: 60 | OctreeNode *root = nullptr; 61 | QList reducible[7]; 62 | int leafCount = 0; // 叶子数量 63 | int maxCount = 20; // 最终得到结果的最大值 64 | }; 65 | 66 | #endif // COLOROCTREE_H 67 | -------------------------------------------------------------------------------- /color_octree/imageutil.h: -------------------------------------------------------------------------------- 1 | #ifndef PIXMAPUTIL_H 2 | #define PIXMAPUTIL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "coloroctree.h" 8 | 9 | #define IMAGE_CALC_PIXEL_MAX_SIZE 128 // 计算的最大边长(大图缩小) 10 | 11 | class ImageUtil 12 | { 13 | public: 14 | static QColor getImageAverageColor(QImage image, int maxPool = IMAGE_CALC_PIXEL_MAX_SIZE); 15 | 16 | static QList extractImageThemeColors(QImage image, int count); 17 | 18 | static QList extractImageThemeColorsInPalette(QImage image, QList paletteColors, int needCount); 19 | 20 | static QColor getInvertColor(QColor color); 21 | 22 | static bool getBgFgColor(QList colors, QColor *bg, QColor *fg); 23 | 24 | static bool getBgFgSgColor(QList colors, QColor *bg, QColor *fg, QColor *sg); 25 | 26 | static bool getBgFgSgColor(QList colors, QColor *bg, QColor *fg, QColor *sbg, QColor *sfg); 27 | 28 | static QColor getFastestColor(QColor bg, QList palette); 29 | 30 | static QColor getFastestColor(QColor bg, QList palette, int enableCount = 2); 31 | 32 | static QColor randomColor(); 33 | 34 | static double calculateLuminance(QColor c); 35 | 36 | static QColor getCloestColorByRGB(QColor color, const QList& colors); 37 | 38 | static QColor getVisuallyClosestColorByCIELAB(const QColor& target, const QList& colors, const QList& colorLabs); 39 | 40 | static QVector3D rgbToXyz(const QColor& color); 41 | 42 | static QVector3D xyzToLab(const QVector3D& xyz); 43 | 44 | static QVector3D rgbToLab(const QColor& color); 45 | 46 | static double deltaE2000(const QVector3D& lab1, const QVector3D& lab2); 47 | 48 | static double deltaE94(const QVector3D& lab1, const QVector3D& lab2); 49 | 50 | static double perceptualRgbDistance(const QColor& c1, const QColor& c2); 51 | }; 52 | 53 | #endif // PIXMAPUTIL_H 54 | -------------------------------------------------------------------------------- /interactive_buttons/appendbutton.cpp: -------------------------------------------------------------------------------- 1 | #include "appendbutton.h" 2 | 3 | AppendButton::AppendButton(QWidget *parent) : InteractiveButtonBase(parent) 4 | { 5 | setUnifyGeomerey(true); 6 | } 7 | 8 | void AppendButton::paintEvent(QPaintEvent *event) 9 | { 10 | InteractiveButtonBase::paintEvent(event); 11 | 12 | if (!show_foreground) return ; // 不显示前景 13 | 14 | double w = _w, h = _h; 15 | double l = _l+w/4+offset_pos.y(), 16 | t = _t+h/4-offset_pos.x(), 17 | r = w*3/4+offset_pos.y(), 18 | b = h*3/4-offset_pos.x(); 19 | double mx = _l+w/2, my = _t+h/2; 20 | 21 | if (click_ani_appearing || click_ani_disappearing) 22 | { 23 | double pro = click_ani_progress / 100.0; 24 | l -= l * pro; 25 | t -= t * pro; 26 | r += (w-r) * pro; 27 | b += (h-b) * pro; 28 | } 29 | 30 | QPainter painter(this); 31 | painter.setPen(icon_color); 32 | painter.setRenderHint(QPainter::Antialiasing, true); 33 | 34 | painter.translate(mx, my); 35 | painter.rotate((90+add_angle) * hover_progress / 100); 36 | painter.translate(-mx, -my); 37 | 38 | painter.drawLine(QPointF(l, (t+b)/2), QPointF(r, (t+b)/2)); 39 | painter.drawLine(QPointF((l+r)/2, t), QPointF((l+r)/2, b)); 40 | } 41 | 42 | void AppendButton::enterEvent(QEvent *event) 43 | { 44 | add_angle = 0; 45 | rotate_speed = 2; 46 | InteractiveButtonBase::enterEvent(event); 47 | } 48 | 49 | void AppendButton::mouseReleaseEvent(QMouseEvent *event) 50 | { 51 | rotate_speed = 2; 52 | InteractiveButtonBase::mouseReleaseEvent(event); 53 | } 54 | 55 | void AppendButton::anchorTimeOut() 56 | { 57 | if (press_progress) 58 | { 59 | add_angle += rotate_speed; 60 | if (add_angle > 360) 61 | { 62 | add_angle -= 360; 63 | if (rotate_speed < 30) // 动态旋转速度 64 | { 65 | rotate_speed++; 66 | } 67 | } 68 | } 69 | 70 | InteractiveButtonBase::anchorTimeOut(); 71 | } 72 | -------------------------------------------------------------------------------- /interactive_buttons/waterzoombutton.cpp: -------------------------------------------------------------------------------- 1 | #include "waterzoombutton.h" 2 | 3 | WaterZoomButton::WaterZoomButton(QString text, QWidget *parent) : InteractiveButtonBase(text, parent) 4 | { 5 | choking = DEFAULT_CHOKING; 6 | radius_zoom = -1; 7 | choking_prop = 0; 8 | } 9 | 10 | WaterZoomButton::WaterZoomButton(QWidget *parent) : InteractiveButtonBase(parent) 11 | { 12 | choking = DEFAULT_CHOKING; 13 | radius_zoom = -1; 14 | choking_prop = 0; 15 | } 16 | 17 | void WaterZoomButton::setChoking(int c) 18 | { 19 | choking = c; 20 | } 21 | 22 | int WaterZoomButton::getChokingSpacing() 23 | { 24 | return choking * 2; 25 | } 26 | 27 | int WaterZoomButton::getDefaultSpacing() 28 | { 29 | return DEFAULT_CHOKING * 2; 30 | } 31 | 32 | void WaterZoomButton::setChokingProp(double p) 33 | { 34 | choking = min(width(), height()) * p; 35 | choking_prop = p; 36 | } 37 | 38 | void WaterZoomButton::setRadiusZoom(int radius) 39 | { 40 | radius_zoom = radius; 41 | } 42 | 43 | void WaterZoomButton::setRadius(int x, int x2) 44 | { 45 | // 注意:最终绘制中只计算 x 的半径,无视 y 的半径 46 | InteractiveButtonBase::setRadius(x); 47 | radius_zoom = x2; 48 | } 49 | 50 | QPainterPath WaterZoomButton::getBgPainterPath() 51 | { 52 | QPainterPath path; 53 | int c; 54 | int r; 55 | if (!hover_progress) 56 | { 57 | c = choking; 58 | r = radius_x; 59 | } 60 | else 61 | { 62 | c = choking * (1 - getNolinearProg(hover_progress, hovering?FastSlower:SlowFaster)); 63 | r = radius_zoom < 0 ? radius_x : 64 | radius_x + (radius_zoom-radius_x) * hover_progress / 100; 65 | } 66 | 67 | if (r) 68 | path.addRoundedRect(QRect(c,c,size().width()-c*2,size().height()-c*2), r, r); 69 | else 70 | path.addRect(QRect(c,c,size().width()-c*2,size().height()-c*2)); 71 | return path; 72 | } 73 | 74 | void WaterZoomButton::resizeEvent(QResizeEvent *event) 75 | { 76 | InteractiveButtonBase::resizeEvent(event); 77 | 78 | if (qAbs(choking_prop)>0.0001) 79 | { 80 | choking = min(width(), height()) * choking_prop; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /interactive_buttons/waterfallbuttongroup.h: -------------------------------------------------------------------------------- 1 | #ifndef WATERFALLBUTTONGROUP_H 2 | #define WATERFALLBUTTONGROUP_H 3 | 4 | #include "waterfloatbutton.h" 5 | 6 | class WaterFallButtonGroup : public QWidget 7 | { 8 | Q_OBJECT 9 | public: 10 | WaterFallButtonGroup(QWidget* parent = nullptr); 11 | 12 | void initStringList(QStringList list, QStringList selected = QStringList()); 13 | void setSelects(QStringList list); 14 | void setSelect(QString text); 15 | void setSelect(int index); 16 | WaterFloatButton *addButton(QString s, bool selected = false); 17 | WaterFloatButton *addButton(QString s, QColor c, bool selected = false); 18 | QStringList getSelectedTexts() const; 19 | QList getSelectedIndexes() const; 20 | void clear(); 21 | 22 | void setColors(QColor normal_bg, QColor hover_bg, QColor press_bg, QColor selected_bg, QColor normal_ft, QColor selected_ft = Qt::transparent); 23 | void setSelectedColor(QColor color); 24 | void updateBtnColors(); 25 | 26 | int count() const; 27 | QList getButtons() const; 28 | 29 | void setSelectable(bool enable); 30 | void setSingleSelect(bool enable); 31 | void updateButtonPositions(); 32 | 33 | protected: 34 | void resizeEvent(QResizeEvent *event) override; 35 | void paintEvent(QPaintEvent *event) override; 36 | 37 | private: 38 | QColor getReverseColor(QColor color); 39 | int getReverseChannel(int x); 40 | void setBtnColors(InteractiveButtonBase* btn); 41 | void changeBtnSelect(InteractiveButtonBase* btn); 42 | void selectBtn(InteractiveButtonBase* btn); 43 | void unselectBtn(InteractiveButtonBase* btn); 44 | 45 | private slots: 46 | void slotButtonClicked(InteractiveButtonBase* btn); 47 | 48 | signals: 49 | void signalSelectChanged(); 50 | void signalClicked(QString s); 51 | void signalSelected(QString s); 52 | void signalUnselected(QString s); 53 | void signalRightClicked(QString s); 54 | 55 | private: 56 | QList btns; 57 | 58 | bool selectable = false; // 是否可选中 59 | bool single_select = false; // 是否单选 60 | QColor normal_bg, hover_bg, press_bg, selected_bg, normal_ft, selected_ft; 61 | 62 | bool _flag_has_change = false; 63 | }; 64 | 65 | #endif // WATERFALLBUTTONGROUP_H 66 | -------------------------------------------------------------------------------- /interactive_buttons/winclosebutton.cpp: -------------------------------------------------------------------------------- 1 | #include "winclosebutton.h" 2 | 3 | WinCloseButton::WinCloseButton(QWidget *parent) 4 | : InteractiveButtonBase (parent), tr_radius(0) 5 | { 6 | setUnifyGeomerey(true); 7 | } 8 | 9 | void WinCloseButton::paintEvent(QPaintEvent *event) 10 | { 11 | InteractiveButtonBase::paintEvent(event); 12 | 13 | if (!show_foreground) return ; // 不显示前景 14 | 15 | int w = _w, h = _h; 16 | int l = _l+w/3, t = _t+h/3, r = w*2/3, b = h*2/3; 17 | int mx = _l+w/2+offset_pos.x(), my = _t+h/2+offset_pos.y(); 18 | 19 | if (click_ani_appearing || click_ani_disappearing) 20 | { 21 | double pro = click_ani_progress / 100.0; 22 | l -= l * pro; 23 | t -= t * pro; 24 | r += (w-r) * pro; 25 | b += (h-b) * pro; 26 | } 27 | 28 | QPainter painter(this); 29 | painter.setPen(QPen(icon_color)); 30 | painter.setRenderHint(QPainter::Antialiasing,true); 31 | if (offset_pos == QPoint(0,0)) 32 | { 33 | painter.drawLine(QPoint(l,t), QPoint(r,b)); 34 | painter.drawLine(QPoint(r,t), QPoint(l,b)); 35 | } 36 | else 37 | { 38 | QPainterPath path; 39 | path.moveTo(QPoint(l,t)); 40 | path.cubicTo(QPoint(l,t), QPoint(mx,my), QPoint(r,b)); 41 | path.moveTo(QPoint(r,t)); 42 | path.cubicTo(QPoint(r,t), QPoint(mx,my), QPoint(l,b)); 43 | 44 | painter.drawPath(path); 45 | } 46 | } 47 | 48 | /** 49 | * 针对圆角的设置 50 | */ 51 | void WinCloseButton::setTopRightRadius(int r) 52 | { 53 | tr_radius = r; 54 | } 55 | 56 | QPainterPath WinCloseButton::getBgPainterPath() 57 | { 58 | if (!tr_radius) 59 | return InteractiveButtonBase::getBgPainterPath(); 60 | 61 | QPainterPath path = InteractiveButtonBase::getBgPainterPath(); 62 | QPainterPath round_path; 63 | round_path.addEllipse(width() - tr_radius - tr_radius, 0, tr_radius*2, tr_radius*2); 64 | QPainterPath corner_path; 65 | corner_path.addRect(width() - tr_radius, 0, tr_radius, tr_radius); 66 | corner_path -= round_path; 67 | path -= corner_path; 68 | return path; 69 | } 70 | 71 | QPainterPath WinCloseButton::getWaterPainterPath(Water water) 72 | { 73 | return InteractiveButtonBase::getWaterPainterPath(water) & WinCloseButton::getBgPainterPath(); 74 | } 75 | -------------------------------------------------------------------------------- /FacileMenu.pro: -------------------------------------------------------------------------------- 1 | QT += core gui svg 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 += interactive_buttons/ \ 19 | color_octree/ \ 20 | facile_menu/ 21 | 22 | SOURCES += \ 23 | color_octree/coloroctree.cpp \ 24 | color_octree/imageutil.cpp \ 25 | facile_menu/facilemenu.cpp \ 26 | facile_menu/facilemenubar.cpp \ 27 | facile_menu/facilemenuitem.cpp \ 28 | interactive_buttons/interactivebuttonbase.cpp \ 29 | main.cpp \ 30 | mainwindow.cpp 31 | 32 | HEADERS += \ 33 | color_octree/coloroctree.h \ 34 | color_octree/imageutil.h \ 35 | facile_menu/facilemenu.h \ 36 | facile_menu/facilemenuanimation.h \ 37 | facile_menu/facilemenubar.h \ 38 | facile_menu/facilemenubarinterface.h \ 39 | facile_menu/facilemenuitem.h \ 40 | interactive_buttons/interactivebuttonbase.h \ 41 | mainwindow.h 42 | 43 | FORMS += \ 44 | mainwindow.ui 45 | 46 | # Default rules for deployment. 47 | qnx: target.path = /tmp/$${TARGET}/bin 48 | else: unix:!android: target.path = /opt/$${TARGET}/bin 49 | !isEmpty(target.path): INSTALLS += target 50 | 51 | RESOURCES += \ 52 | resources.qrc 53 | 54 | DISTFILES += \ 55 | README.md \ 56 | screenshots/menubar.gif \ 57 | screenshots/picture.gif \ 58 | android/AndroidManifest.xml \ 59 | android/build.gradle \ 60 | android/gradle/wrapper/gradle-wrapper.jar \ 61 | android/gradle/wrapper/gradle-wrapper.properties \ 62 | android/gradlew \ 63 | android/gradlew.bat \ 64 | android/res/values/libs.xml 65 | 66 | contains(ANDROID_TARGET_ARCH,x86) { 67 | ANDROID_PACKAGE_SOURCE_DIR = \ 68 | $$PWD/android 69 | } 70 | -------------------------------------------------------------------------------- /mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 381 10 | 179 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 30 21 | 80 22 | 75 23 | 23 24 | 25 | 26 | 27 | 菜单 28 | 29 | 30 | 31 | 32 | 33 | 120 34 | 80 35 | 75 36 | 23 37 | 38 | 39 | 40 | 菜单2 41 | 42 | 43 | 44 | 45 | 46 | 210 47 | 80 48 | 75 49 | 23 50 | 51 | 52 | 53 | 菜单3 54 | 55 | 56 | 57 | 58 | 59 | 0 60 | 0 61 | 341 62 | 31 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 0 71 | 0 72 | 381 73 | 21 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | InteractiveButtonBase 82 | QPushButton 83 |
interactivebuttonbase.h
84 |
85 | 86 | FacileMenuBar 87 | QWidget 88 |
facilemenubar.h
89 | 1 90 |
91 |
92 | 93 | 94 |
95 | -------------------------------------------------------------------------------- /interactive_buttons/winsidebarbutton.cpp: -------------------------------------------------------------------------------- 1 | #include "winsidebarbutton.h" 2 | 3 | WinSidebarButton::WinSidebarButton(QWidget* parent) 4 | : InteractiveButtonBase (parent), tl_radius(0) 5 | { 6 | setUnifyGeomerey(true); 7 | } 8 | 9 | void WinSidebarButton::paintEvent(QPaintEvent *event) 10 | { 11 | InteractiveButtonBase::paintEvent(event); 12 | 13 | if (!show_foreground) return ; // 不显示前景 14 | 15 | int dx = offset_pos.x(), dy = offset_pos.y(); 16 | int l = _l + _w/3+dx, t = _t + _h/3+dy, w = _w/3, h = _h/3; 17 | 18 | QPainter painter(this); 19 | painter.setPen(QPen(icon_color)); 20 | painter.setRenderHint(QPainter::Antialiasing,true); 21 | 22 | if (click_ani_appearing) 23 | { 24 | double pro = click_ani_progress / 100.0; 25 | if (getState()) 26 | pro = 1 - pro; 27 | painter.drawEllipse(l+w/2-w*pro/2, t+h/2-h*pro/2, w*pro, h*pro); 28 | 29 | if (getState()) 30 | pro = getSpringBackProgress(click_ani_progress, 50) / 100.0; 31 | else 32 | pro = 1 - click_ani_progress / 100.0; 33 | 34 | l = _l + _w/3+dx; 35 | t = _t + _h/3+dy; 36 | w = _w / 3; 37 | h = _h / 3; 38 | 39 | l += w/2.0 - w*pro/2; 40 | t += h/2.0 - h*pro/2; 41 | w *= pro; 42 | h *= pro; 43 | 44 | QPainterPath path; 45 | path.addEllipse(l, t, w, h); 46 | painter.fillPath(path, icon_color); 47 | } 48 | else if (getState()) 49 | { 50 | QPainterPath path; 51 | path.addEllipse(l, t, w, h); 52 | painter.fillPath(path, icon_color); 53 | } 54 | else 55 | { 56 | painter.drawEllipse(l, t, w, h); 57 | } 58 | } 59 | 60 | void WinSidebarButton::slotClicked() 61 | { 62 | if (getState()) 63 | setState(false); 64 | else 65 | setState(true); 66 | return InteractiveButtonBase::slotClicked(); 67 | } 68 | 69 | /** 70 | * 针对圆角的设置 71 | */ 72 | void WinSidebarButton::setTopLeftRadius(int r) 73 | { 74 | tl_radius = r; 75 | } 76 | 77 | QPainterPath WinSidebarButton::getBgPainterPath() 78 | { 79 | if (!tl_radius) 80 | return InteractiveButtonBase::getBgPainterPath(); 81 | 82 | QPainterPath path = InteractiveButtonBase::getBgPainterPath(); 83 | QPainterPath round_path; 84 | round_path.addEllipse(0, 0, tl_radius * 2, tl_radius * 2); 85 | QPainterPath corner_path; 86 | corner_path.addRect(0, 0, tl_radius, tl_radius); 87 | corner_path -= round_path; 88 | path -= corner_path; 89 | return path; 90 | } 91 | 92 | QPainterPath WinSidebarButton::getWaterPainterPath(Water water) 93 | { 94 | return InteractiveButtonBase::getWaterPainterPath(water) & WinSidebarButton::getBgPainterPath(); 95 | } -------------------------------------------------------------------------------- /interactive_buttons/winrestorebutton.cpp: -------------------------------------------------------------------------------- 1 | #include "winrestorebutton.h" 2 | 3 | WinRestoreButton::WinRestoreButton(QWidget* parent) 4 | : InteractiveButtonBase(parent) 5 | { 6 | setUnifyGeomerey(true); 7 | } 8 | 9 | void WinRestoreButton::paintEvent(QPaintEvent* event) 10 | { 11 | InteractiveButtonBase::paintEvent(event); 12 | 13 | if (!show_foreground) return ; // 不显示前景 14 | 15 | // 画出现一角的矩形 16 | int w = _w, h = _h; 17 | int dx = offset_pos.x(), dy = offset_pos.y(); 18 | QRect br; 19 | if (click_ani_appearing || click_ani_disappearing) 20 | { 21 | double pro = click_ani_progress / 800.0; 22 | br = QRect( 23 | _l+(w/3+dx) - (w/3+dx)*pro, 24 | _t+(h/3+dy) - (h/3+dy)*pro, 25 | w/3 + (w*2/3)*pro, 26 | h/3 + (h*2/3)*pro 27 | ); 28 | } 29 | else 30 | { 31 | br = QRect(_l+w/3+dx, _t+h/3+dy, w/3, h/3); 32 | } 33 | 34 | // 画原来的矩形 35 | QPainter painter(this); 36 | painter.setPen(QPen(icon_color)); 37 | painter.drawRect(br); 38 | 39 | dx /= 2; dy /= 2; 40 | int l = _l+w*4/9+dx, t = _t+h*2/9+dy, r = _l+w*7/9+dx, b = _t+h*5/9+dy; 41 | if (click_ani_appearing || click_ani_disappearing) 42 | { 43 | double pro = click_ani_progress / 800.0; 44 | l -= l*pro; 45 | t -= t*pro; 46 | r += (w-r)*pro; 47 | b += (h-b)*pro; 48 | } 49 | QPoint topLeft(l, t), topRight(r, t), bottomLeft(l, b), bottomRight(r, b); 50 | QListpoints; 51 | 52 | /* 两个矩形一样大的,所以运行的时候,需要有三大类: 53 | * 1、完全重合(可以视为下一点任意之一) 54 | * 2、有一个点落在矩形内(4种情况) 55 | * 3、完全不重合 56 | * 根据3大类共6种进行判断 57 | */ 58 | if (br.topLeft() == topLeft) 59 | { 60 | points << topLeft << topRight << bottomRight << bottomLeft << topLeft; 61 | } 62 | else if (br.contains(topLeft)) // 左上角在矩形内 63 | { 64 | points << QPoint(br.right()+1, t) << topRight << bottomRight << bottomLeft << QPoint(l, br.bottom()+1); 65 | } 66 | else if (br.contains(topRight)) // 右上角在矩形内 67 | { 68 | points << QPoint(r, br.bottom()+1) << bottomRight << bottomLeft << topLeft << QPoint(br.left(), t); 69 | } 70 | else if (br.contains(bottomLeft)) // 左下角在矩形内(默认) 71 | { 72 | points << QPoint(l, br.top()) << topLeft << topRight << bottomRight << QPoint(br.right()+1, b); 73 | } 74 | else if (br.contains(bottomRight)) // 右下角在矩形内 75 | { 76 | points << QPoint(br.left(), b) << bottomLeft << topLeft << topRight << QPoint(r, br.top()); 77 | } 78 | else // 没有重合 79 | { 80 | points << topLeft << topRight << bottomRight << bottomLeft << topLeft; 81 | } 82 | 83 | if (points.size() > 1) 84 | { 85 | QPainterPath path; 86 | path.moveTo(points.at(0)); 87 | for (int i = 1; i < points.size(); ++i) 88 | path.lineTo(points.at(i)); 89 | QColor color(icon_color); 90 | color.setAlpha(color.alpha()*0.8); 91 | painter.setPen(QPen(color)); 92 | painter.drawPath(path); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /interactive_buttons/watercirclebutton.cpp: -------------------------------------------------------------------------------- 1 | #include "watercirclebutton.h" 2 | 3 | WaterCircleButton::WaterCircleButton(QWidget* parent) : InteractiveButtonBase (parent), in_circle(false), radius(16) 4 | { 5 | 6 | } 7 | 8 | WaterCircleButton::WaterCircleButton(QIcon icon, QWidget *parent) : InteractiveButtonBase (icon, parent), in_circle(false), radius(16) 9 | { 10 | 11 | } 12 | 13 | WaterCircleButton::WaterCircleButton(QPixmap pixmap, QWidget *parent) : InteractiveButtonBase (pixmap, parent), in_circle(false), radius(16) 14 | { 15 | 16 | } 17 | 18 | void WaterCircleButton::enterEvent(QEvent *event) 19 | { 20 | 21 | } 22 | 23 | void WaterCircleButton::leaveEvent(QEvent *event) 24 | { 25 | if (in_circle && !pressing && !inArea(mapFromGlobal(QCursor::pos()))) 26 | { 27 | in_circle = false; 28 | InteractiveButtonBase::leaveEvent(event); 29 | } 30 | } 31 | 32 | void WaterCircleButton::mousePressEvent(QMouseEvent *event) 33 | { 34 | if (in_circle || (!hovering && inArea(event->pos()))) 35 | return InteractiveButtonBase::mousePressEvent(event); 36 | } 37 | 38 | void WaterCircleButton::mouseReleaseEvent(QMouseEvent *event) 39 | { 40 | if (pressing) 41 | { 42 | InteractiveButtonBase::mouseReleaseEvent(event); 43 | 44 | if (leave_after_clicked || (!inArea(event->pos()) && !pressing)) // 鼠标移出 45 | { 46 | in_circle = false; 47 | InteractiveButtonBase::leaveEvent(nullptr); 48 | } 49 | } 50 | } 51 | 52 | void WaterCircleButton::mouseMoveEvent(QMouseEvent *event) 53 | { 54 | bool is_in = inArea(event->pos()); 55 | 56 | if (is_in && !in_circle)// 鼠标移入 57 | { 58 | in_circle = true; 59 | InteractiveButtonBase::enterEvent(nullptr); 60 | } 61 | else if (!is_in && in_circle && !pressing) // 鼠标移出 62 | { 63 | in_circle = false; 64 | InteractiveButtonBase::leaveEvent(nullptr); 65 | } 66 | 67 | if (in_circle) 68 | InteractiveButtonBase::mouseMoveEvent(event); 69 | } 70 | 71 | void WaterCircleButton::resizeEvent(QResizeEvent *event) 72 | { 73 | center_pos = geometry().center() - geometry().topLeft(); 74 | radius = min(size().width(), size().height())/ 2; 75 | 76 | return InteractiveButtonBase::resizeEvent(event); 77 | } 78 | 79 | QPainterPath WaterCircleButton::getBgPainterPath() 80 | { 81 | QPainterPath path; 82 | int w = size().width(), h = size().height(); 83 | QRect rect(w/2-radius, h/2-radius, radius*2, radius*2); 84 | path.addEllipse(rect); 85 | return path; 86 | } 87 | 88 | QPainterPath WaterCircleButton::getWaterPainterPath(InteractiveButtonBase::Water water) 89 | { 90 | QPainterPath path = InteractiveButtonBase::getWaterPainterPath(water) & getBgPainterPath(); 91 | return path; 92 | } 93 | 94 | void WaterCircleButton::simulateStatePress(bool s) 95 | { 96 | in_circle = true; 97 | InteractiveButtonBase::simulateStatePress(s); 98 | in_circle = false; 99 | } 100 | 101 | bool WaterCircleButton::inArea(QPoint point) 102 | { 103 | return (point - center_pos).manhattanLength() <= radius; 104 | } 105 | -------------------------------------------------------------------------------- /facile_menu/facilemenuanimation.h: -------------------------------------------------------------------------------- 1 | #ifndef FACILEMENUANIMATION_H 2 | #define FACILEMENUANIMATION_H 3 | 4 | #include 5 | #include "interactivebuttonbase.h" 6 | 7 | class FacileBackgrounWidget : QWidget 8 | { 9 | 10 | }; 11 | 12 | class FacileSwitchWidget : public QWidget 13 | { 14 | Q_OBJECT 15 | Q_PROPERTY(double switch_ani READ getSwitchAni WRITE setSwitchAni) 16 | public: 17 | FacileSwitchWidget(QWidget* w1, QWidget* w2) : QWidget(nullptr) 18 | { 19 | // 设置属性 20 | setWindowFlags(Qt::Popup | Qt::FramelessWindowHint); 21 | 22 | // 设置圆角矩形 23 | int borderRadius = 0; 24 | if (borderRadius) 25 | { 26 | QPixmap pixmap(width(), height()); 27 | pixmap.fill(Qt::transparent); 28 | QPainter pix_ptr(&pixmap); 29 | pix_ptr.setRenderHint(QPainter::Antialiasing, true); 30 | QPainterPath path; 31 | path.addRoundedRect(0, 0, width(), height(), borderRadius, borderRadius); 32 | pix_ptr.fillPath(path, Qt::white); 33 | setMask(pixmap.mask()); 34 | } 35 | 36 | // 创建状态 37 | pixmap1 = QPixmap(w1->size()); 38 | w1->render(&pixmap1); 39 | pixmap2 = QPixmap(w2->size()); 40 | w2->render(&pixmap2); 41 | 42 | // 动画设置 43 | int duration = 150; 44 | 45 | // 绘制动画 46 | QPropertyAnimation* ani = new QPropertyAnimation(this, "switch_ani"); 47 | ani->setStartValue(0); 48 | ani->setEndValue(255); 49 | ani->setDuration(duration); 50 | ani->setEasingCurve(QEasingCurve::OutSine); 51 | ani->start(); 52 | connect(ani, &QPropertyAnimation::stateChanged, this, [=](QPropertyAnimation::State state) { 53 | if (state == QPropertyAnimation::State::Stopped) 54 | ani->deleteLater(); 55 | }); 56 | 57 | // 移动动画 58 | ani = new QPropertyAnimation(this, "geometry"); 59 | ani->setStartValue(w1->geometry()); 60 | ani->setEndValue(w2->geometry()); 61 | ani->setDuration(duration); 62 | ani->setEasingCurve(QEasingCurve::OutSine); 63 | connect(ani, SIGNAL(valueChanged(const QVariant &)), this, SLOT(update())); 64 | ani->start(); 65 | connect(ani, &QPropertyAnimation::stateChanged, this, [=](QPropertyAnimation::State state) { 66 | if (state == QPropertyAnimation::State::Stopped) 67 | { 68 | emit finished(); 69 | ani->deleteLater(); 70 | this->deleteLater(); 71 | } 72 | }); 73 | 74 | this->show(); 75 | } 76 | 77 | protected: 78 | void paintEvent(QPaintEvent *event) override 79 | { 80 | QWidget::paintEvent(event); 81 | 82 | QPainter painter(this); 83 | painter.setOpacity(qMax(0.0, 192 - switch_ani) / 192); 84 | painter.drawPixmap(rect(), pixmap1); 85 | painter.setOpacity(qMax(0.0, switch_ani-64) / 192); 86 | painter.drawPixmap(rect(), pixmap2); 87 | } 88 | 89 | double getSwitchAni() const 90 | { 91 | return switch_ani; 92 | } 93 | 94 | void setSwitchAni(double x) 95 | { 96 | this->switch_ani = x; 97 | } 98 | 99 | signals: 100 | void finished(); 101 | 102 | private: 103 | QPixmap pixmap1; 104 | QPixmap pixmap2; 105 | double alpha1 = 0; 106 | double alpha2 = 0; 107 | double switch_ani = 0; 108 | }; 109 | 110 | #endif // FACILEMENUANIMATION_H 111 | -------------------------------------------------------------------------------- /interactive_buttons/pointmenubutton.cpp: -------------------------------------------------------------------------------- 1 | #include "pointmenubutton.h" 2 | 3 | PointMenuButton::PointMenuButton(QWidget *parent) : InteractiveButtonBase(parent) 4 | { 5 | setUnifyGeomerey(true); 6 | radius = 1; 7 | setClickAniDuration(600); 8 | } 9 | 10 | void PointMenuButton::mousePressEvent(QMouseEvent *event) 11 | { 12 | InteractiveButtonBase::slotClicked(); 13 | return InteractiveButtonBase::mousePressEvent(event); 14 | } 15 | 16 | void PointMenuButton::paintEvent(QPaintEvent * event) 17 | { 18 | InteractiveButtonBase::paintEvent(event); 19 | 20 | if (!show_foreground) return ; // 不显示前景 21 | 22 | int w = _w, h = _h; 23 | int l = _l+w/3, t = _t+h/3, r = w*2/3, b = h*2/3; 24 | int mx = _l+w/2+offset_pos.x(), my = _t+h/2+offset_pos.y(); 25 | 26 | // 画笔 27 | QPainter painter(this); 28 | painter.setPen(QPen(icon_color)); 29 | painter.setRenderHint(QPainter::Antialiasing,true); 30 | 31 | if (click_ani_appearing) 32 | { 33 | int midx = (l+r) / 2; 34 | int move_radius = (b-t)*3/4/2; 35 | QPainterPath path; 36 | 37 | // 第一个点 38 | if (click_ani_progress <= ANI_STEP_3) // 画圈 39 | { 40 | double tp = click_ani_progress / (double)ANI_STEP_3; 41 | QPoint o(midx, t + move_radius); 42 | QPoint p(o.x() - move_radius * sin(PI * tp), o.y() - move_radius * cos(PI * tp)); 43 | path.addEllipse(p.x()-radius, p.y()-radius, radius<<1, radius<<1); 44 | } 45 | else if (click_ani_progress <= ANI_STEP_3*2) // 静止 46 | { 47 | QPoint o(midx, t + move_radius*2); 48 | path.addEllipse(o.x()-radius, o.y()-radius, radius<<1, radius<<1); 49 | } 50 | else // 下移 51 | { 52 | double tp = (click_ani_progress-ANI_STEP_3*2) / (100.0 - ANI_STEP_3*2); 53 | QPoint p(midx, b-(1-tp)*(b-t)/6); 54 | path.addEllipse(p.x()-radius, p.y()-radius, radius<<1, radius<<1); 55 | } 56 | 57 | // 第二个点 58 | if (click_ani_progress <= (100-ANI_STEP_3*2)) // 静止 59 | { 60 | QPoint o(midx, (t+b)/2); 61 | path.addEllipse(o.x()-radius, o.y()-radius, radius<<1, radius<<1); 62 | } 63 | else // 不动 64 | { 65 | QPoint o(midx, (t+b)/2); 66 | path.addEllipse(o.x()-radius, o.y()-radius, radius<<1, radius<<1); 67 | } 68 | 69 | // 第三个点 70 | if (click_ani_progress <= ANI_STEP_3) // 静止 71 | { 72 | QPoint o(midx, b); 73 | path.addEllipse(o.x()-radius, o.y()-radius, radius<<1, radius<<1); 74 | } 75 | else if (click_ani_progress > ANI_STEP_3 && click_ani_progress <= ANI_STEP_3*2) // 画圈 76 | { 77 | double tp = (click_ani_progress-ANI_STEP_3) / (double)ANI_STEP_3; 78 | QPoint o(midx, b - move_radius); 79 | QPoint p(o.x() + move_radius * sin(PI * tp), o.y() + move_radius * cos(PI * tp)); 80 | path.addEllipse(p.x()-radius, p.y()-radius, radius<<1, radius<<1); 81 | } 82 | else // 上移 83 | { 84 | double tp = (click_ani_progress-ANI_STEP_3*2) / (100-ANI_STEP_3*2); 85 | QPoint p(midx, t+(1-tp)*(b-t)/6); 86 | path.addEllipse(p.x()-radius, p.y()-radius, radius<<1, radius<<1); 87 | } 88 | 89 | painter.fillPath(path, QColor(icon_color)); 90 | } 91 | else 92 | { 93 | QPainterPath path; 94 | path.addEllipse((l+r)/2-radius, t-radius, radius<<1, radius<<1); 95 | path.addEllipse((l+r)/2-radius, (t+b)/2-radius, radius<<1, radius<<1); 96 | path.addEllipse((l+r)/2-radius, b-radius, radius<<1, radius<<1); 97 | painter.fillPath(path, QColor(icon_color)); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /interactive_buttons/winmenubutton.cpp: -------------------------------------------------------------------------------- 1 | #include "winmenubutton.h" 2 | 3 | WinMenuButton::WinMenuButton(QWidget* parent) 4 | : InteractiveButtonBase (parent) 5 | { 6 | setUnifyGeomerey(true); 7 | } 8 | 9 | void WinMenuButton::paintEvent(QPaintEvent *event) 10 | { 11 | InteractiveButtonBase::paintEvent(event); 12 | 13 | if (!show_foreground) return ; // 不显示前景 14 | 15 | int w = _w, h = _h; 16 | int dx = offset_pos.x(), dy = offset_pos.y(); 17 | 18 | QPainter painter(this); 19 | painter.setPen(QPen(icon_color)); 20 | 21 | if (click_ani_appearing) 22 | { 23 | double pro = click_ani_progress / 100.0; 24 | if (!getState()) 25 | pro = 1 - pro; 26 | 27 | int len = w/3; 28 | int l = _l+w/3, r = _l+w*2/3, t = _t+h/3 + pro*h/3; 29 | painter.drawLine(QPoint(l+dx/4,t+dy/4), QPoint(r+dx/4,t+dy/4)); 30 | 31 | l = _l+w/3+pro*w/24, r = _l+w*2/3-pro*w/24, t = _t+w/2+pro*h*5/18; 32 | painter.drawLine(QPoint(l+dx/2,t+dy/2), QPoint(r+dx/2,t+dy/2)); 33 | 34 | l = _l+w/3+pro*w/12, r = _l+w*2/3-pro*w/12, t = _t+w*2/3+pro*h*2/9; 35 | painter.drawLine(QPoint(l+dx,t+dy), QPoint(r+dx,t+dy)); 36 | 37 | /*int half_len = w/6;//quick_sqrt(w/3*w/3 + h/3*h/3) / 2; // 长度 38 | painter.setRenderHint(QPainter::Antialiasing,true); 39 | 40 | // 第一个点 41 | { 42 | int mx = w/2; 43 | int my = h/3 + pro*h/6; 44 | double angle = pro*PI*5/4; 45 | int sx = mx - half_len*cos(angle); 46 | int sy = my - half_len*sin(angle); 47 | int ex = mx + half_len*cos(angle); 48 | int ey = my + half_len*sin(angle); 49 | painter.drawLine(QPoint(sx,sy), QPoint(ex,ey)); 50 | } 51 | 52 | // 第三个点 53 | { 54 | int mx = w/2; 55 | int my = h*2/3 - pro*h/6; 56 | double angle = pro*PI*3/4; 57 | int sx = mx - half_len*cos(angle); 58 | int sy = my - half_len*sin(angle); 59 | int ex = mx + half_len*cos(angle); 60 | int ey = my + half_len*sin(angle); 61 | painter.drawLine(QPoint(sx,sy), QPoint(ex,ey)); 62 | } 63 | 64 | // 第二个点(设置透明度) 65 | { 66 | int mx = w/2; 67 | int my = h/2; 68 | double angle = pro*PI; 69 | int sx = mx - half_len*cos(angle); 70 | int sy = my - half_len*sin(angle); 71 | int ex = mx + half_len*cos(angle); 72 | int ey = my + half_len*sin(angle); 73 | QColor color(icon_color); 74 | color.setAlpha(color.alpha() * (1-pro)); 75 | painter.setPen(QPen(color)); 76 | painter.drawLine(QPoint(sx,sy), QPoint(ex,ey)); 77 | }*/ 78 | } 79 | else if (getState()) 80 | { 81 | painter.drawLine(_l+w/3+dx/4, _t+h*2/3+dy/4, w*2/3+dx/4,h*2/3+dy/4); 82 | painter.drawLine(_l+w/3+w/24+dx/2, _t+h*7/9+dy/2, w*2/3-w/24+dx/2, h*7/9+dy/2); 83 | painter.drawLine(_l+w/3+w/12+dx, _t+h*8/9+dy, w*2/3-w/12+dx, h*8/9+dy); 84 | 85 | /*painter.drawLine(QPoint(0.39*w, 0.39*h), QPoint(0.61*w, 0.61*h)); 86 | painter.drawLine(QPoint(0.39*w, 0.61*h), QPoint(0.61*w, 0.39*h));*/ 87 | } 88 | else 89 | { 90 | painter.drawLine(QPoint(_l+w/3+dx/4,_t+h/3+dy/4), QPoint(_l+w*2/3+dx/4,_t+h/3+dy/4)); 91 | painter.drawLine(QPoint(_l+w/3+dx/2,_t+h/2+dy/2), QPoint(_l+w*2/3+dx/2,_t+h/2+dy/2)); 92 | painter.drawLine(QPoint(_l+w/3+dx,_t+h*2/3+dy), QPoint(_l+w*2/3+dx,_t+h*2/3+dy)); 93 | } 94 | } 95 | 96 | void WinMenuButton::slotClicked() 97 | { 98 | if (getState()) 99 | setState(false); 100 | else 101 | setState(true); 102 | return InteractiveButtonBase::slotClicked(); 103 | } 104 | -------------------------------------------------------------------------------- /facile_menu/color_list.txt: -------------------------------------------------------------------------------- 1 | 撫子 #dc9fb4 2 | 紅梅 #e16b8c 3 | 蘇芳 #8e354a 4 | 退紅 #f8c3cd 5 | 一斥染 #f4a7b9 6 | 桑染 #64363c 7 | 桃 #f596aa 8 | 苺 #b5495b 9 | 薄紅 #e87a90 10 | 今様 #d05a6e 11 | 中紅 #db4d6d 12 | 桜 #fedfe1 13 | 梅鼠 #9e7a7a 14 | 韓紅花 #d0104c 15 | 燕脂 #9f353a 16 | 紅 #cb1b45 17 | 鴇 #eea9a9 18 | 長春 #bf6766 19 | 深緋 #86473f 20 | 桜鼠 #b19693 21 | 甚三紅 #eb7a77 22 | 小豆 #954a45 23 | 蘇芳香 #a96360 24 | 赤紅 #cb4042 25 | 真朱 #ab3b3a 26 | 灰桜 #d7c4bb 27 | 栗梅 #904840 28 | 海老茶 #734338 29 | 銀朱 #c73e3a 30 | 黒鳶 #554236 31 | 紅鳶 #994639 32 | 曙 #f19483 33 | 紅樺 #b54434 34 | 水がき #b9887d 35 | 珊瑚朱 #f17c67 36 | 紅檜皮 #884c3a 37 | 猩猩緋 #e83015 38 | 鉛丹 #d75455 39 | 芝翫茶 #b55d4c 40 | 檜皮 #854836 41 | 柿渋 #a35e47 42 | 緋 #cc543a 43 | 鳶 #724832 44 | 紅緋 #f75c2f 45 | 栗皮茶 #6a4028 46 | 弁柄 #9a5034 47 | 照柿 #c46243 48 | 江戸茶 #af5f3c 49 | 洗朱 #fb966e 50 | 百塩茶 #724938 51 | 唐茶 #b47157 52 | ときがら茶 #db8e71 53 | 黄丹 #f05e1c 54 | 纁 #ed784a 55 | 遠州茶 #ca7853 56 | 樺茶 #b35c37 57 | 焦茶 #563f2e 58 | 赤香 #e3916e 59 | 雀茶 #8f5a3c 60 | 宍 #f0a986 61 | 宗伝唐茶 #a0674b 62 | 樺 #c1693c 63 | 深支子 #fb9966 64 | 胡桃 #947a6d 65 | 代赭 #a36336 66 | 洗柿 #e79460 67 | 黄櫨染 #7d532c 68 | 赤朽葉 #c78550 69 | 礪茶 #985f2a 70 | 赤白橡 #e1a679 71 | 煎茶 #855b32 72 | 萱草 #fc9f4d 73 | 洒落柿 #ffba84 74 | 紅鬱金 #e98b2a 75 | 梅染 #e9a368 76 | 枇杷茶 #b17844 77 | 丁子茶 #96632e 78 | 憲法染 #43341b 79 | 琥珀 #ca7a2c 80 | 薄柿 #ecb88a 81 | 伽羅 #78552b 82 | 丁子染 #b07736 83 | 柴染 #967249 84 | 朽葉 #e2943b 85 | 金茶 #c7802d 86 | 狐 #9b6e23 87 | 煤竹 #6e552f 88 | 薄香 #ebb471 89 | 砥粉 #d7b98e 90 | 銀煤竹 #82663a 91 | 黄土 #b68e55 92 | 白茶 #bc9f77 93 | 媚茶 #876633 94 | 黄唐茶 #c18a26 95 | 山吹 #ffb11b 96 | 山吹茶 #d19826 97 | 櫨染 #dda52d 98 | 桑茶 #c99833 99 | 玉子 #f9bf45 100 | 白橡 #dcb879 101 | 黄橡 #ba9132 102 | 玉蜀黍 #e8b647 103 | 花葉 #f7c242 104 | 生壁 #7d6c46 105 | 鳥の子 #dac9a6 106 | 浅黄 #fad689 107 | 黄朽葉 #d9ab42 108 | 梔子 #f6c555 109 | 籐黄 #ffc408 110 | 鬱金 #efbb24 111 | 芥子 #caad5f 112 | 肥後煤竹 #8d742a 113 | 利休白茶 #b4a582 114 | 灰汁 #877f6c 115 | 利休茶 #897d55 116 | 路考茶 #74673e 117 | 菜種油 #a28c37 118 | 鶯茶 #6c6024 119 | 黄海松茶 #867835 120 | 海松茶 #62592c 121 | 刈安 #e9cd4c 122 | 菜の花 #f7d94c 123 | 黄蘗 #fbe251 124 | 蒸栗 #d9cd90 125 | 青朽葉 #ada142 126 | 女郎花 #ddd23b 127 | 鶸茶 #a5a051 128 | 鶸 #bec23f 129 | 鶯 #6c6a2d 130 | 柳茶 #939650 131 | 苔 #838a2d 132 | 麹塵 #b1b479 133 | 璃寛茶 #616138 134 | 藍媚茶 #4b4e2a 135 | 海松 #5b622e 136 | 千歳茶 #4d5139 137 | 梅幸茶 #89916b 138 | 鶸萌黄 #90b44b 139 | 柳染 #91ad70 140 | 裏柳 #b5caa0 141 | 岩井茶 #646a58 142 | 萌黄 #7ba23f 143 | 苗 #86c166 144 | 柳煤竹 #4a593d 145 | 松葉 #42602d 146 | 青丹 #516e41 147 | 薄青 #91b493 148 | 柳鼠 #808f7c 149 | 常磐 #1b813e 150 | 若竹 #5dac81 151 | 千歳緑 #36563c 152 | 緑 #227d51 153 | 白緑 #a8d8b9 154 | 老竹 #6a8372 155 | 木賊 #2d6d4b 156 | 御納戸茶 #465d4c 157 | 緑青 #24936e 158 | 錆青磁 #86a697 159 | 青竹 #00896c 160 | ビロード #096148 161 | 虫襖 #20604f 162 | 藍海松茶 #0f4c3a 163 | 沈香茶 #4f726c 164 | 青緑 #00aa90 165 | 青磁 #69b0ac 166 | 鉄 #26453d 167 | 水浅葱 #66bab7 168 | 青碧 #268785 169 | 錆鉄御納戸 #405b55 170 | 高麗納戸 #305a56 171 | 白群 #78c2c4 172 | 御召茶 #376b6d 173 | 瓶覗 #a5dee4 174 | 深川鼠 #77969a 175 | 錆浅葱 #6699a1 176 | 水 #81c7d4 177 | 浅葱 #33a6b8 178 | 御納戸 #0c4842 179 | 藍 #0d5661 180 | 新橋 #0089a7 181 | 錆御納戸 #336774 182 | 鉄御納戸 #255359 183 | 花浅葱 #1e88a8 184 | 藍鼠 #566c73 185 | 舛花 #577c8a 186 | 空 #58b2dc 187 | 熨斗目花 #2b5f75 188 | 千草 #3a8fb7 189 | 御召御納戸 #2e5c6e 190 | 縹 #006284 191 | 勿忘草 #7db9de 192 | 群青 #51a8dd 193 | 露草 #2ea9df 194 | 黒橡 #0b1013 195 | 紺 #0f2540 196 | 褐 #08192d 197 | 瑠璃 #005caf 198 | 瑠璃紺 #0b346e 199 | 紅碧 #7b90d2 200 | 藤鼠 #6e75a4 201 | 鉄紺 #261e47 202 | 紺青 #113285 203 | 紅掛花 #4e4f97 204 | 紺桔梗 #211e55 205 | 藤 #8b81c3 206 | 二藍 #70649a 207 | 楝 #9b90c2 208 | 藤紫 #8a6bbe 209 | 桔梗 #6a4c9c 210 | 紫苑 #8f77b5 211 | 滅紫 #533d5b 212 | 薄 #b28fce 213 | 半 #986db2 214 | 江戸紫 #77428d 215 | 紫紺 #3c2f41 216 | 深紫 #4a225d 217 | 菫 #66327c 218 | 紫 #592c63 219 | 菖蒲 #6f3381 220 | 藤煤竹 #574c57 221 | 紅藤 #b481bb 222 | 黒紅 #3f2b36 223 | 茄子紺 #572a3f 224 | 葡萄鼠 #5e3d50 225 | 鳩羽鼠 #72636e 226 | 杜若 #622954 227 | 蒲葡 #6d2e5b 228 | 牡丹 #c1328e 229 | 梅紫 #a8497a 230 | 似紫 #562e37 231 | 躑躅 #e03c8a 232 | 紫鳶 #60373e 233 | 白練 #fcfaf2 234 | 胡粉 #fffffb 235 | 白鼠 #bdc0ba 236 | 銀鼠 #91989f 237 | 鉛 #787878 238 | 灰 #828282 239 | 素鼠 #787d7b 240 | 利休鼠 #707c74 241 | 鈍 #656765 242 | 青鈍 #535953 243 | 溝鼠 #4f4f48 244 | 紅消鼠 #52433d 245 | 藍墨茶 #373c38 246 | 檳榔子染 #3a3226 247 | 消炭 #434343 248 | 墨 #1c1c1c 249 | 黒 #080808 250 | 呂 #0c0c0c -------------------------------------------------------------------------------- /interactive_buttons/infobutton.cpp: -------------------------------------------------------------------------------- 1 | #include "infobutton.h" 2 | 3 | InfoButton::InfoButton(QWidget *parent) : InteractiveButtonBase(parent) 4 | { 5 | setUnifyGeomerey(true); 6 | // hover_speed = 1; 7 | } 8 | 9 | void InfoButton::paintEvent(QPaintEvent *event) 10 | { 11 | InteractiveButtonBase::paintEvent(event); 12 | 13 | if (!show_foreground) return ; // 不显示前景 14 | 15 | QPainter painter(this); 16 | painter.setRenderHint(QPainter::Antialiasing, true); 17 | painter.setPen(icon_color); 18 | QPainterPath path; 19 | 20 | double l = _l, t = _t, w = _w, h = _h; 21 | 22 | double cu = 2; // 点的粗细 23 | if (!hover_progress) // 显示感叹号 24 | { 25 | // 画点 26 | path.addEllipse(QRectF(l+w/2-cu, t+h/4, cu*2, cu*2)); 27 | 28 | // 画线 29 | path.addRoundedRect(QRectF(l+w/2-cu/2, t+h*3/8+cu, cu, h/2 - cu*2), cu/2, cu/2); 30 | } 31 | else if (hover_progress < 100) // 转换动画 32 | { 33 | double prop = hover_progress / 100.0; 34 | 35 | // 眼睛出现 36 | double ra = cu * prop * 1.2; 37 | path.addEllipse(l+w/4-ra, t+h/4-ra, ra*2, ra*2); 38 | path.addEllipse(l+w*3/4-ra, t+h/4-ra, ra*2, ra*2); 39 | 40 | // 鼻子下降 41 | double top = t+h/4 + (h/4) * prop; 42 | path.addEllipse(QRectF(l+w/2-cu, top, cu*2, cu*2)); 43 | 44 | double h_mv = w / 4 * prop; // 胡子端点 45 | top += cu*2 + h/8*(1-prop); // +点的高度 +点和线距离 46 | 47 | // 左胡子移动 48 | QPainterPath pathl; 49 | pathl.moveTo(l+w/2, top); // 竖线顶端 50 | pathl.cubicTo(QPointF(l+w*8/16, t+h*7/8), QPointF(l+w*5/16+w*3/16*(1-prop), t+h*6/8), QPointF(l+w/2-h_mv, t+h*11/16+h*3/16*(1-prop))); 51 | painter.drawPath(pathl); 52 | 53 | // 右胡子移动 54 | QPainterPath pathr; 55 | pathr.moveTo(l+w/2, top); 56 | pathr.cubicTo(QPointF(l+w*8/16, t+h*7/8), QPointF(l+w*11/16-w*3/16*(1-prop), t+h*6/8), QPointF(l+w/2+h_mv, t+h*11/16+h*3/16*(1-prop))); 57 | painter.drawPath(pathr); 58 | } 59 | else // 显示Hover静态猫头 60 | { 61 | // 眼睛 62 | if (pressing) // 横线 63 | { 64 | if (offset_pos.x() * offset_pos.x() + offset_pos.y() + offset_pos.y() > this->width() * this->height() / 4) // 显示 x_x 65 | { 66 | painter.drawLine(QPointF(l+w/4-cu, t+h/4-cu), QPointF(l+w/4+cu, t+h/4+cu)); 67 | painter.drawLine(QPointF(l+w/4-cu, t+h/4+cu+0.5), QPointF(l+w/4+cu, t+h/4-cu)); 68 | painter.drawLine(QPointF(l+w*3/4-cu, t+h/4-cu), QPointF(l+w*3/4+cu, t+h/4+cu)); 69 | painter.drawLine(QPointF(l+w*3/4-cu, t+h/4+cu+0.5), QPointF(l+w*3/4+cu, t+h/4-cu)); 70 | } 71 | else // 显示 -_- 72 | { 73 | painter.drawLine(QPointF(l+w/4-cu*2, t+h/4), QPointF(l+w/4+cu*2, t+h/4)); 74 | painter.drawLine(QPointF(l+w*3/4-cu*2, t+h/4), QPointF(l+w*3/4+cu*2, t+h/4)); 75 | } 76 | 77 | if (tongue) 78 | { 79 | double prop = press_progress / 100.0; 80 | // TODO: 添加吐舌头……太麻烦,懒得写了 81 | } 82 | } 83 | else // 点 84 | { 85 | double ra = cu * 1.2; 86 | path.addEllipse(l+w/4-ra, t+h/4-ra, ra*2, ra*2); 87 | path.addEllipse(l+w*3/4-ra, t+h/4-ra, ra*2, cu*2); 88 | } 89 | 90 | // 鼻子点 91 | path.addEllipse(l+w/2-cu, t+h/2-cu/2, cu*2, cu*2); 92 | 93 | // 左胡子 94 | QPainterPath pathl; 95 | pathl.moveTo(l+w/2, t+h*4/8); // 中心顶点 96 | pathl.cubicTo(QPointF(l+w*8/16, t+h*7/8), QPointF(l+w*5/16, t+h*6/8), QPointF(l+w/4, t+h*11/16)); 97 | painter.drawPath(pathl); 98 | 99 | // 右胡子 100 | QPainterPath pathr; 101 | pathr.moveTo(l+w/2, t+h*4/8); 102 | pathr.cubicTo(QPointF(l+w*8/16, t+h*7/8), QPointF(l+w*11/16, t+h*6/8), QPointF(l+w*3/4, t+h*11/16)); 103 | painter.drawPath(pathr); 104 | } 105 | painter.fillPath(path, icon_color); 106 | } 107 | 108 | void InfoButton::mousePressEvent(QMouseEvent *event) 109 | { 110 | if (event->button() == Qt::LeftButton) 111 | { 112 | // 随机吐舌头 113 | tongue = qrand() % 10 == 0; 114 | } 115 | 116 | return InteractiveButtonBase::mousePressEvent(event); 117 | } 118 | -------------------------------------------------------------------------------- /facile_menu/facilemenuitem.h: -------------------------------------------------------------------------------- 1 | #ifndef FACILEMENUITEM_H 2 | #define FACILEMENUITEM_H 3 | 4 | #include "interactivebuttonbase.h" 5 | 6 | class FacileMenu; 7 | class FacileMenuItem; 8 | 9 | typedef std::function const FuncType; 10 | typedef std::function const FuncIntType; 11 | typedef std::function const FuncStringStringType; 12 | typedef std::function const FuncItemType; 13 | typedef std::function const FuncItemIntType; 14 | 15 | class FacileMenuItem : public InteractiveButtonBase 16 | { 17 | public: 18 | FacileMenuItem(QWidget* parent = nullptr); 19 | FacileMenuItem(QString t, QWidget* parent = nullptr); 20 | FacileMenuItem(QIcon i, QWidget* parent = nullptr); 21 | FacileMenuItem(QIcon i, QString t, QWidget* parent = nullptr); 22 | FacileMenuItem(QPixmap p, QString t, QWidget* parent = nullptr); 23 | 24 | FacileMenuItem* setEnabled(bool e); 25 | FacileMenuItem* setCheckable(bool c); 26 | bool isCheckable() const; 27 | FacileMenuItem* setChecked(bool c); 28 | bool isChecked(); 29 | FacileMenuItem* setKey(Qt::Key key); 30 | bool isKey(Qt::Key key) const; 31 | FacileMenuItem* setSubMenu(FacileMenu* menu); 32 | bool isSubMenu() const; 33 | bool isLinger() const; 34 | FacileMenuItem* setData(QVariant data); 35 | QVariant getData(); 36 | FacileMenuItem *setDynamicCreate(bool dynamic = true); 37 | void setDynamicCreateState(short state); 38 | short getDynamicCreateState() const; 39 | 40 | FacileMenuItem* tip(QString sc); 41 | FacileMenuItem* tip(bool exp, QString sc); 42 | FacileMenuItem* tooltip(QString tt); 43 | FacileMenuItem* tooltip(bool exp, QString tt); 44 | FacileMenuItem* triggered(FuncType func); 45 | FacileMenuItem* triggered(bool exp, FuncType func); 46 | FacileMenuItem* disable(bool exp = true); // 满足情况下触发,不满足不变,下同 47 | FacileMenuItem* enable(bool exp = true); 48 | FacileMenuItem* hide(bool exp = true); 49 | FacileMenuItem* visible(bool exp = true); 50 | FacileMenuItem* check(bool exp = true); 51 | FacileMenuItem* uncheck(bool exp = true); 52 | FacileMenuItem* toggle(bool exp = true); 53 | FacileMenuItem* autoToggle(); 54 | FacileMenuItem* text(bool exp, QString str); 55 | FacileMenuItem* text(bool exp, QString tru, QString fal); 56 | FacileMenuItem* fgColor(QColor color); 57 | FacileMenuItem* fgColor(bool exp, QColor color); 58 | FacileMenuItem* bgColor(QColor color); 59 | FacileMenuItem* bgColor(bool exp, QColor color); 60 | FacileMenuItem* prefix(bool exp, QString pfix); 61 | FacileMenuItem* suffix(bool exp, QString sfix, bool inLeftParenthesis = true); 62 | FacileMenuItem* prefix(QString pfix); 63 | FacileMenuItem* suffix(QString sfix, bool inLeftParenthesis = true); 64 | FacileMenuItem* icon(bool exp, QIcon icon); 65 | FacileMenuItem* borderR(int radius = 3, QColor co = Qt::transparent); 66 | FacileMenuItem* linger(); 67 | FacileMenuItem* lingerText(QString textAfterClick); 68 | FacileMenuItem* bind(bool &val); 69 | FacileMenuItem* longPress(FuncType func); 70 | FacileMenuItem* textAfterClick(QString newText); 71 | FacileMenuItem* textAfterClick(FuncStringStringType func); 72 | 73 | FacileMenuItem* ifer(bool exp); 74 | FacileMenuItem* elifer(bool exp); 75 | FacileMenuItem* elser(); 76 | FacileMenuItem* exiter(bool exp = true); 77 | FacileMenuItem* ifer(bool exp, FuncItemType func, FuncItemType elseFunc = nullptr); 78 | FacileMenuItem* switcher(int value); 79 | FacileMenuItem* caser(int value, FuncType func); 80 | FacileMenuItem* caser(int value); 81 | FacileMenuItem* breaker(); 82 | FacileMenuItem* defaulter(); 83 | 84 | FacileMenu* subMenu(); 85 | 86 | protected: 87 | void paintEvent(QPaintEvent *event) override; 88 | void drawIconBeforeText(QPainter &painter, QRect icon_rect) override; 89 | 90 | FacileMenuItem* createTempItem(bool thisIsParent = true); 91 | 92 | private: 93 | Qt::Key key; 94 | bool checkable = false; 95 | bool trigger_linger = false; // 点击后是否保存菜单 96 | FacileMenu* sub_menu = nullptr; 97 | QString shortcut_tip = ""; // 快捷键提示 98 | FacileMenuItem* parent_menu_item_in_if = nullptr; // elser/caser专用 99 | int switch_value = 0; // switcher的值,用来和caser比较(不需要breaker……) 100 | bool switch_matched = true; 101 | QVariant data; 102 | short dynamic_create_state = 0; // -1未触发,0未启用,1已出发 103 | }; 104 | 105 | #endif // FACILEMENUITEM_H 106 | -------------------------------------------------------------------------------- /facile_menu/facilemenubar.cpp: -------------------------------------------------------------------------------- 1 | #include "facilemenubar.h" 2 | #include "facilemenuanimation.h" 3 | 4 | FacileMenuBar::FacileMenuBar(QWidget *parent) : QWidget(parent) 5 | { 6 | hlayout = new QHBoxLayout(this); 7 | hlayout->setAlignment(Qt::AlignLeft); 8 | hlayout->setSpacing(0); 9 | hlayout->setMargin(0); 10 | setLayout(hlayout); 11 | } 12 | 13 | /// 鼠标是否在这个区域内 14 | /// @param pos 绝对位置 15 | /// @return 所在的 MenuButton 索引,-1表示不在 16 | int FacileMenuBar::isCursorInArea(QPoint pos) const 17 | { 18 | QPoint mPos = mapFromGlobal(pos); 19 | if (!rect().contains(mPos)) 20 | return -1; 21 | for (int i = 0; i < buttons.size(); i++) 22 | { 23 | if (buttons.at(i)->geometry().contains(mPos)) 24 | return i; 25 | } 26 | return -1; 27 | } 28 | 29 | int FacileMenuBar::currentIndex() const 30 | { 31 | return _currentIndex; 32 | } 33 | 34 | /// 准备激活第index个菜单 35 | /// 如果不是参数menu,则激活该菜单 36 | bool FacileMenuBar::triggerIfNot(int index, void *menu) 37 | { 38 | // 非菜单栏中的按钮,隐藏全部 39 | FacileMenu* m = static_cast(menu); 40 | if (index < 0 || index >= buttons.size()) 41 | { 42 | if (m) 43 | m->hide(); 44 | return true; 45 | } 46 | 47 | // 切换按钮 48 | if (menus.at(index) == menu) 49 | { 50 | return false; 51 | } 52 | 53 | if (m && enableAnimation) 54 | { 55 | // 带动画的trigger 56 | switchTrigger(index, menus.indexOf(m)); 57 | } 58 | else 59 | { 60 | if (m) 61 | m->close(); 62 | trigger(index); 63 | } 64 | 65 | return true; 66 | } 67 | 68 | void FacileMenuBar::addMenu(QString name, FacileMenu *menu) 69 | { 70 | auto btn = createButton(name, menu); 71 | 72 | buttons.append(btn); 73 | menus.append(menu); 74 | hlayout->addWidget(btn); 75 | } 76 | 77 | void FacileMenuBar::insertMenu(int index, QString name, FacileMenu *menu) 78 | { 79 | auto btn = createButton(name, menu); 80 | 81 | buttons.insert(index, btn); 82 | menus.insert(index, menu); 83 | hlayout->insertWidget(index, btn); 84 | } 85 | 86 | void FacileMenuBar::deleteMenu(int index) 87 | { 88 | if (index < 0 || index >= buttons.size()) 89 | return ; 90 | buttons.takeAt(index)->deleteLater(); 91 | menus.takeAt(index)->deleteLater(); 92 | } 93 | 94 | int FacileMenuBar::count() const 95 | { 96 | return buttons.size(); 97 | } 98 | 99 | void FacileMenuBar::setAnimationEnabled(bool en) 100 | { 101 | this->enableAnimation = en; 102 | } 103 | 104 | /// 显示菜单 105 | void FacileMenuBar::trigger(int index) 106 | { 107 | if (index < 0 || index >= buttons.size()) 108 | return ; 109 | 110 | if (aniWidget) 111 | { 112 | aniWidget->deleteLater(); 113 | aniWidget = nullptr; 114 | } 115 | 116 | InteractiveButtonBase* btn = buttons.at(index); 117 | btn->setBgColor(FacileMenu::hover_bg); 118 | QRect rect(btn->mapToGlobal(QPoint(0, 0)), btn->size()); 119 | if (enableAnimation) 120 | menus.at(index)->setAppearAnimation(true); // 要重新设置一下,因为取消掉了 121 | menus.at(index)->exec(rect, true); 122 | _currentIndex = index; 123 | } 124 | 125 | void FacileMenuBar::switchTrigger(int index, int prevIndex) 126 | { 127 | if (index < 0 || index >= buttons.size()) 128 | return ; 129 | if (prevIndex < 0 || prevIndex >= buttons.size()) 130 | return trigger(index); 131 | 132 | InteractiveButtonBase* btn = buttons.at(index); 133 | QRect rect(btn->mapToGlobal(QPoint(0, 0)), btn->size()); 134 | 135 | FacileMenu* m = menus.at(index); 136 | m->setAppearAnimation(false); // 必须要关闭显示动画 137 | menus.at(prevIndex)->hide(); // 避免影响背景 138 | m->exec(rect, true); 139 | 140 | if (aniWidget) 141 | { 142 | aniWidget->deleteLater(); 143 | aniWidget = nullptr; 144 | } 145 | 146 | auto w = new FacileSwitchWidget(menus.at(prevIndex), menus.at(index)); 147 | aniWidget = w; 148 | QPoint pos = m->pos(); 149 | m->move(-10000 - m->width(), -10000 - m->height()); // 要保持显示,才能监控鼠标位置;因为可能立刻移开了 150 | btn->setBgColor(FacileMenu::hover_bg); // 上面的hide会触发关闭事件,取消颜色 151 | connect(w, &FacileSwitchWidget::finished, w, [=]{ 152 | m->move(pos); 153 | m->show(); 154 | aniWidget = nullptr; // 自动deleteLater 155 | }); 156 | _currentIndex = index; 157 | } 158 | 159 | InteractiveButtonBase *FacileMenuBar::createButton(QString name, FacileMenu *menu) 160 | { 161 | InteractiveButtonBase* btn = new InteractiveButtonBase(name, this); 162 | // TODO: name 识别并注册快捷键 163 | connect(btn, &InteractiveButtonBase::clicked, this, [=]{ 164 | int index = buttons.indexOf(btn); 165 | if (!menus.at(index)->isHidden()) 166 | { 167 | // 如果是正在显示状态 168 | // 但是理论上来说不可能,因为这种情况是点不到按钮的 169 | menus.at(index)->hide(); 170 | } 171 | else 172 | { 173 | // 点击触发菜单 174 | trigger(buttons.indexOf(btn)); 175 | } 176 | }); 177 | connect(menu, &FacileMenu::signalHidden, btn, [=]{ 178 | btn->setBgColor(Qt::transparent); 179 | _currentIndex = -1; 180 | }); 181 | 182 | btn->adjustMinimumSize(); 183 | btn->setRadius(5); 184 | btn->setFixedForePos(); 185 | 186 | menu->setMenuBar(this); 187 | menu->setAttribute(Qt::WA_DeleteOnClose, false); 188 | // menu->setAppearAnimation(false); // 设置显示动画 189 | menu->setDisappearAnimation(false); // 设置关闭动画 190 | menu->setSubMenuShowOnCursor(false); 191 | if (enableAnimation) 192 | menu->setBorderRadius(0); 193 | return btn; 194 | } 195 | -------------------------------------------------------------------------------- /color_octree/coloroctree.cpp: -------------------------------------------------------------------------------- 1 | #include "coloroctree.h" 2 | 3 | ColorOctree::ColorOctree() 4 | { 5 | } 6 | 7 | ColorOctree::ColorOctree(QImage image, int maxSize, int maxCount) 8 | { 9 | // 缩减尺寸 10 | long long hCount = image.width(); 11 | long long vCount = image.height(); 12 | if (hCount > maxSize || vCount > maxSize) // 数量过大,按比例缩减 13 | { 14 | double prop = (double)maxSize / qMax(hCount, vCount); 15 | image = image.scaledToWidth(image.width() * prop); // 缩放到最大大小 16 | } 17 | 18 | // 开始建树 19 | buildTree(image, maxCount); 20 | } 21 | 22 | ColorOctree::~ColorOctree() 23 | { 24 | if (root) 25 | delete root; 26 | } 27 | 28 | /** 29 | * 构建八叉树 30 | */ 31 | void ColorOctree::buildTree(QImage image, int maxCount) 32 | { 33 | this->maxCount = maxCount; 34 | if (root) 35 | delete root; 36 | root = new OctreeNode(); 37 | memset(root, 0, sizeof(OctreeNode)); 38 | leafCount = 0; 39 | 40 | // 先转换为颜色,再添加到八叉树节点 41 | // 据说使用按行读取的方式能加快效率 42 | int w = image.width(), h = image.height(); 43 | for (int y = 0; y < h; y++) 44 | { 45 | QRgb *line = (QRgb *)image.scanLine(y); 46 | for (int x = 0; x < w; x++) 47 | { 48 | int r = qRed(line[x]), g = qGreen(line[x]), b = qBlue(line[x]); 49 | RGB rgb{r, g, b}; 50 | // 添加颜色到八叉树 51 | addColor(root, &rgb, 0); 52 | 53 | // 合并颜色 54 | // 有两个选择:addColor完就减(快),全部add再减(慢) 55 | // 但是这结果一模一样…… 56 | while (leafCount > maxCount) 57 | { 58 | // 加返回值会影响性能,但是避免了结果颜色少时死循环的问题 59 | // 个人猜测,可能是因为0层全都是叶子节点 60 | // 例如7缩减到6时,八叉树第0层有7个叶子节点,无法缩减 61 | if (!reduceTree()) // 当 maxCount <= 7 时,很容易触发 62 | break; 63 | } 64 | } 65 | } 66 | } 67 | 68 | /** 69 | * 返回结果 70 | */ 71 | QList ColorOctree::result() 72 | { 73 | QList counts; 74 | colorStats(root, &counts); 75 | std::sort(counts.begin(), counts.end(), [=](ColorCount a, ColorCount b) { 76 | if (a.count > b.count) 77 | return true; 78 | if (a.count < b.count) 79 | return false; 80 | return strcmp(a.color, b.color) < 0; 81 | }); 82 | return counts; 83 | } 84 | 85 | void ColorOctree::addColor(ColorOctree::OctreeNode *node, RGB *color, int level) 86 | { 87 | if (node->isLeaf) // 加到叶子节点 88 | { 89 | node->pixelCount++; 90 | node->red += color->red; 91 | node->green += color->green; 92 | node->blue += color->blue; 93 | } 94 | else // 加到下几层的叶子节点 95 | { 96 | /** 97 | * eg. 98 | * R: 10101101 99 | * G: 00101101 100 | * B: 10010010 101 | * 102 | * idx: 50616616 103 | */ 104 | unsigned char r = (color->red >> (7 - level)) & 1; 105 | unsigned char g = (color->green >> (7 - level)) & 1; 106 | unsigned char b = (color->blue >> (7 - level)) & 1; 107 | int idx = (r << 2) + (g << 1) + b; 108 | 109 | if (!node->children[idx]) 110 | { 111 | // 创建下一层 112 | OctreeNode *tmp = node->children[idx] = new OctreeNode; 113 | memset(tmp, 0, sizeof(OctreeNode)); 114 | if (level == 7) // 最后一层 115 | { 116 | tmp->isLeaf = true; 117 | leafCount++; 118 | } 119 | else // 不是最后一层 120 | { 121 | reducible[level].push_front(tmp); // 放入缩减的列表中 122 | } 123 | } 124 | 125 | addColor(node->children[idx], color, level + 1); 126 | } 127 | } 128 | 129 | bool ColorOctree::reduceTree() 130 | { 131 | // 找到最深的叶子 132 | int lv = 6; // 从最后第2层(第7层)开始合并自己的叶子 133 | while (reducible[lv].empty() && lv >= 0) 134 | lv--; 135 | if (lv < 0) 136 | return false; 137 | 138 | // 移除该节点 139 | OctreeNode *node = reducible[lv].front(); 140 | reducible[lv].pop_front(); 141 | 142 | // 合并该节点的子节点 143 | long long r = 0, g = 0, b = 0; 144 | int count = 0; 145 | for (int i = 0; i < 8; i++) 146 | { 147 | if (!node->children[i]) 148 | continue; 149 | r += node->children[i]->red; 150 | g += node->children[i]->green; 151 | b += node->children[i]->blue; 152 | count += node->children[i]->pixelCount; 153 | leafCount--; 154 | 155 | delete node->children[i]; 156 | node->children[i] = nullptr; 157 | } 158 | 159 | node->isLeaf = true; 160 | node->red = r; 161 | node->green = g; 162 | node->blue = b; 163 | node->pixelCount = count; 164 | leafCount++; 165 | return true; 166 | } 167 | 168 | /** 169 | * 获取颜色结果 170 | */ 171 | void ColorOctree::colorStats(ColorOctree::OctreeNode *node, QList *colors) 172 | { 173 | if (node->isLeaf) 174 | { 175 | int r = node->red / node->pixelCount; 176 | int g = node->green / node->pixelCount; 177 | int b = node->blue / node->pixelCount; 178 | 179 | ColorCount cnt; 180 | sprintf(cnt.color, "%.2X%.2X%.2X", r, g, b); 181 | cnt.count = node->pixelCount; 182 | cnt.colorValue = (r << 16) + (g << 8) + b; 183 | cnt.red = r; 184 | cnt.green = g; 185 | cnt.blue = b; 186 | 187 | colors->push_back(cnt); 188 | return; 189 | } 190 | 191 | for (int i = 0; i < 8; i++) 192 | { 193 | if (node->children[i]) 194 | { 195 | colorStats(node->children[i], colors); 196 | } 197 | } 198 | } 199 | 200 | -------------------------------------------------------------------------------- /interactive_buttons/threedimenbutton.cpp: -------------------------------------------------------------------------------- 1 | #include "threedimenbutton.h" 2 | 3 | ThreeDimenButton::ThreeDimenButton(QWidget* parent) : InteractiveButtonBase (parent) 4 | { 5 | setMouseTracking(true); 6 | aop_w = width() / AOPER; 7 | aop_h = height() / AOPER; 8 | 9 | shadow_effect = new QGraphicsDropShadowEffect(this); 10 | shadow_effect->setOffset(0, 0); 11 | shadow_effect->setColor(QColor(0x88, 0x88, 0x88, 0x64)); 12 | shadow_effect->setBlurRadius(10); 13 | setGraphicsEffect(shadow_effect); 14 | 15 | setJitterAni(false); 16 | } 17 | 18 | void ThreeDimenButton::enterEvent(QEvent *event) 19 | { 20 | 21 | } 22 | 23 | void ThreeDimenButton::leaveEvent(QEvent *event) 24 | { 25 | if (in_rect && !pressing && !inArea(mapFromGlobal(QCursor::pos()))) 26 | { 27 | in_rect = false; 28 | InteractiveButtonBase::leaveEvent(nullptr); 29 | } 30 | 31 | // 不return,因为区域不一样 32 | } 33 | 34 | void ThreeDimenButton::mousePressEvent(QMouseEvent *event) 35 | { 36 | // 因为上面可能有控件,所以可能无法监听到 enter 事件 37 | if (!in_rect && inArea(event->pos())) // 鼠标移入 38 | { 39 | in_rect = true; 40 | InteractiveButtonBase::enterEvent(nullptr); 41 | } 42 | 43 | if (in_rect) 44 | return InteractiveButtonBase::mousePressEvent(event); 45 | } 46 | 47 | void ThreeDimenButton::mouseReleaseEvent(QMouseEvent *event) 48 | { 49 | if (pressing) 50 | { 51 | InteractiveButtonBase::mouseReleaseEvent(event); 52 | 53 | if (leave_after_clicked || (!inArea(event->pos()) && !pressing)) // 鼠标移出 54 | { 55 | in_rect = false; 56 | InteractiveButtonBase::leaveEvent(nullptr); 57 | } 58 | } 59 | } 60 | 61 | void ThreeDimenButton::mouseMoveEvent(QMouseEvent *event) 62 | { 63 | bool is_in = inArea(event->pos()); 64 | 65 | if (is_in && !in_rect)// 鼠标移入 66 | { 67 | in_rect = true; 68 | InteractiveButtonBase::enterEvent(nullptr); 69 | } 70 | else if (!is_in && in_rect && !pressing) // 鼠标移出 71 | { 72 | in_rect = false; 73 | InteractiveButtonBase::leaveEvent(nullptr); 74 | } 75 | 76 | if (in_rect) 77 | InteractiveButtonBase::mouseMoveEvent(event); 78 | } 79 | 80 | void ThreeDimenButton::resizeEvent(QResizeEvent *event) 81 | { 82 | aop_w = width() / AOPER; 83 | aop_h = height() / AOPER; 84 | return InteractiveButtonBase::resizeEvent(event); 85 | } 86 | 87 | void ThreeDimenButton::anchorTimeOut() 88 | { 89 | // 因为上面有控件挡住了,所以需要定时监控move情况 90 | mouse_pos = mapFromGlobal(QCursor::pos()); 91 | if (!pressing && !inArea(mouse_pos)) // 鼠标移出 92 | { 93 | InteractiveButtonBase::leaveEvent(nullptr); 94 | } 95 | 96 | InteractiveButtonBase::anchorTimeOut(); 97 | 98 | // 修改阴影的位置 99 | if (offset_pos == QPoint(0,0)) 100 | shadow_effect->setOffset(0, 0); 101 | else 102 | { 103 | if (offset_pos.manhattanLength() > SHADE) 104 | { 105 | double sx = -SHADE * offset_pos.x() / offset_pos.manhattanLength(); 106 | double sy = -SHADE * offset_pos.y() / offset_pos.manhattanLength(); 107 | shadow_effect->setOffset(sx*hover_progress/100, sy*hover_progress/100); 108 | } 109 | else 110 | { 111 | shadow_effect->setOffset(-offset_pos.x()*hover_progress/100, -offset_pos.y()*hover_progress/100); 112 | } 113 | } 114 | } 115 | 116 | QPainterPath ThreeDimenButton::getBgPainterPath() 117 | { 118 | QPainterPath path; 119 | if (hover_progress) // 鼠标悬浮效果 120 | { 121 | /** 122 | * 位置比例 = 悬浮比例 × 距离比例 123 | * 坐标位置 ≈ 鼠标方向偏移 124 | */ 125 | double hp = hover_progress / 100.0; 126 | QPoint o(width()/2, height()/2); // 中心点 127 | QPointF m = limitPointXY(mapFromGlobal(QCursor::pos())-o, width()/2, height()/2); // 当前鼠标的点 128 | QPointF f = limitPointXY(offset_pos, aop_w, aop_h); // 偏移点(压力中心) 129 | 130 | QPointF lt, lb, rb, rt; 131 | // 左上角 132 | { 133 | QPointF p = QPoint(aop_w, aop_h) - o; 134 | double prob = dian_cheng(m, p) / dian_cheng(p, p); 135 | lt = o + (p) * (1-prob*hp/AOPER); 136 | } 137 | // 右上角 138 | { 139 | QPointF p = QPoint(width() - aop_w, aop_h) - o; 140 | double prob = dian_cheng(m, p) / dian_cheng(p, p); 141 | rt = o + (p) * (1-prob*hp/AOPER); 142 | } 143 | // 左下角 144 | { 145 | QPointF p = QPoint(aop_w, height() - aop_h) - o; 146 | double prob = dian_cheng(m, p) / dian_cheng(p, p); 147 | lb = o + (p) * (1-prob*hp/AOPER); 148 | } 149 | // 右下角 150 | { 151 | QPointF p = QPoint(width() - aop_w, height() - aop_h) - o; 152 | double prob = dian_cheng(m, p) / dian_cheng(p, p); 153 | rb = o + (p) * (1-prob*hp/AOPER); 154 | } 155 | 156 | path.moveTo(lt); 157 | path.lineTo(lb); 158 | path.lineTo(rb); 159 | path.lineTo(rt); 160 | path.lineTo(lt); 161 | } 162 | else 163 | { 164 | // 简单的path,提升性能用 165 | path.addRect(aop_w, aop_h, width()-aop_w*2, height()-aop_h*2); 166 | } 167 | 168 | return path; 169 | } 170 | 171 | QPainterPath ThreeDimenButton::getWaterPainterPath(InteractiveButtonBase::Water water) 172 | { 173 | QRect circle(water.point.x() - water_radius*water.progress/100, 174 | water.point.y() - water_radius*water.progress/100, 175 | water_radius*water.progress/50, 176 | water_radius*water.progress/50); 177 | QPainterPath path; 178 | path.addEllipse(circle); 179 | return path & getBgPainterPath(); 180 | } 181 | 182 | void ThreeDimenButton::simulateStatePress(bool s, bool a) 183 | { 184 | in_rect = true; 185 | InteractiveButtonBase::simulateStatePress(s, a); 186 | in_rect = false; 187 | } 188 | 189 | bool ThreeDimenButton::inArea(QPointF point) 190 | { 191 | return !(point.x() < aop_w 192 | || point.x() > width()-aop_w 193 | || point.y() < aop_h 194 | || point.y() > height()-aop_h); 195 | } 196 | 197 | /** 198 | * 计算两个向量的叉积 199 | * 获取压力值 200 | */ 201 | double ThreeDimenButton::cha_cheng(QPointF a, QPointF b) 202 | { 203 | return a.x() * b.y() - b.x()* a.y(); 204 | } 205 | 206 | double ThreeDimenButton::dian_cheng(QPointF a, QPointF b) 207 | { 208 | return a.x() * b.x() + a.y() * b.y(); 209 | } 210 | 211 | QPointF ThreeDimenButton::limitPointXY(QPointF v, int w, int h) 212 | { 213 | // 注意:成立时,v.x != 0,否则除零错误 214 | if (v.x() < -w) 215 | { 216 | v.setY(v.y()*-w/v.x()); 217 | v.setX(-w); 218 | } 219 | 220 | if (v.x() > w) 221 | { 222 | v.setY(v.y()*w/v.x()); 223 | v.setX(w); 224 | } 225 | 226 | if (v.y() < -h) 227 | { 228 | v.setX(v.x()*-h/v.y()); 229 | v.setY(-h); 230 | } 231 | 232 | if (v.y() > h) 233 | { 234 | v.setX(v.x()*h/v.y()); 235 | v.setY(h); 236 | } 237 | 238 | return v; 239 | } 240 | -------------------------------------------------------------------------------- /interactive_buttons/waterfloatbutton.cpp: -------------------------------------------------------------------------------- 1 | #include "waterfloatbutton.h" 2 | 3 | WaterFloatButton::WaterFloatButton(QWidget *parent) : InteractiveButtonBase(parent), 4 | in_area(false), mwidth(16), radius(8) 5 | { 6 | fore_enabled = false; 7 | fore_paddings.left = fore_paddings.right = radius; 8 | } 9 | 10 | WaterFloatButton::WaterFloatButton(QString s, QWidget *parent) : InteractiveButtonBase(s, parent), 11 | in_area(false), mwidth(16), radius(8) 12 | { 13 | fore_enabled = false; 14 | fore_paddings.left = fore_paddings.right = radius; 15 | } 16 | 17 | void WaterFloatButton::enterEvent(QEvent *event) 18 | { 19 | 20 | } 21 | 22 | void WaterFloatButton::leaveEvent(QEvent *event) 23 | { 24 | if (in_area && !pressing && !inArea(mapFromGlobal(QCursor::pos()))) 25 | { 26 | in_area = false; 27 | InteractiveButtonBase::leaveEvent(event); 28 | } 29 | } 30 | 31 | void WaterFloatButton::mousePressEvent(QMouseEvent *event) 32 | { 33 | if (in_area) 34 | return InteractiveButtonBase::mousePressEvent(event); 35 | } 36 | 37 | void WaterFloatButton::mouseReleaseEvent(QMouseEvent *event) 38 | { 39 | if (pressing) 40 | { 41 | InteractiveButtonBase::mouseReleaseEvent(event); 42 | 43 | if (!pressing && !inArea(event->pos())) 44 | { 45 | in_area = false; 46 | InteractiveButtonBase::leaveEvent(event); 47 | } 48 | } 49 | } 50 | 51 | void WaterFloatButton::mouseMoveEvent(QMouseEvent *event) 52 | { 53 | bool is_in = inArea(event->pos()); 54 | 55 | if (!in_area && is_in) // 鼠标移入 56 | { 57 | in_area = true; 58 | InteractiveButtonBase::enterEvent(nullptr); 59 | } 60 | else if (in_area && !is_in && !pressing) // 鼠标移出 61 | { 62 | in_area = false; 63 | InteractiveButtonBase::leaveEvent(nullptr); 64 | } 65 | 66 | if (in_area) 67 | InteractiveButtonBase::mouseMoveEvent(event); 68 | } 69 | 70 | void WaterFloatButton::resizeEvent(QResizeEvent *event) 71 | { 72 | int w = geometry().width(), h = geometry().height(); 73 | if (h >= w * 4) // 宽度为准 74 | radius = w / 4; 75 | else 76 | radius = h/2; 77 | mwidth = (w-radius*2); 78 | 79 | return InteractiveButtonBase::resizeEvent(event); 80 | } 81 | 82 | void WaterFloatButton::paintEvent(QPaintEvent *event) 83 | { 84 | InteractiveButtonBase::paintEvent(event); 85 | 86 | QPainter painter(this); 87 | // painter.setRenderHint(QPainter::SmoothPixmapTransform, true); 88 | painter.setRenderHint(QPainter::Antialiasing,true); 89 | 90 | // 鼠标悬浮进度 91 | QColor edge_color = hover_bg; 92 | int pro = 0; 93 | if (hover_progress > 0 || press_progress || waters.size()) 94 | { 95 | if (water_animation) 96 | { 97 | /** 不用判断 water 是出现还是消失状态 98 | * 如果一直悬浮的话,颜色不会变 99 | * 而如果是点一下立马移开,文字会出现一种“渐隐渐现”的效果 100 | */ 101 | if (waters.size()) 102 | pro = max(hover_progress, waters.last().progress); 103 | else 104 | pro = hover_progress; 105 | } 106 | else 107 | { 108 | max(hover_progress, press_progress); 109 | } 110 | edge_color.setAlpha(255 * (100 - pro) / 100); 111 | } 112 | 113 | // 画边框 114 | QPainterPath path; 115 | if (show_foreground) 116 | { 117 | path = getBgPainterPath(); // 整体背景 118 | 119 | // 出现动画 120 | if (show_ani_appearing && show_ani_progress != 100 && border_bg.alpha() != 0) 121 | { 122 | int pw = size().width() * show_ani_progress / 100; 123 | QRect rect(0, 0, pw, size().height()); 124 | QPainterPath rect_path; 125 | rect_path.addRect(rect); 126 | path &= rect_path; 127 | 128 | int x = show_ani_point.x(), y = show_ani_point.y(); 129 | int gen = quick_sqrt(x*x + y*y); 130 | x = - water_radius * x / gen; // 动画起始中心点横坐标 反向 131 | y = - water_radius * y / gen; // 动画起始中心点纵坐标 反向 132 | } 133 | if (border_bg.alpha() != 0) // 如果有背景,则不进行画背景线条 134 | { 135 | painter.setPen(border_bg); 136 | painter.drawPath(path); 137 | } 138 | } 139 | 140 | // 画文字 141 | if ((self_enabled || fore_enabled) && !text.isEmpty()) 142 | { 143 | QRect rect = QRect(QPoint(0,0), size()); 144 | QColor color; 145 | if (pro) 146 | { 147 | if (auto_text_color) 148 | { 149 | QColor aim_color = isLightColor(hover_bg) ? QColor(0, 0, 0) : QColor(255, 255, 255); 150 | color = QColor( 151 | text_color.red() + (aim_color.red() - text_color.red()) * pro / 100, 152 | text_color.green() + (aim_color.green() - text_color.green()) * pro / 100, 153 | text_color.blue() + (aim_color.blue() - text_color.blue()) * pro / 100, 154 | 255); 155 | } 156 | painter.setPen(color); 157 | } 158 | else 159 | { 160 | color = text_color; 161 | color.setAlpha(255); 162 | } 163 | painter.setPen(color); 164 | if (font_size > 0) 165 | { 166 | QFont font = painter.font(); 167 | font.setPointSize(font_size); 168 | painter.setFont(font); 169 | } 170 | painter.drawText(rect, Qt::AlignCenter, text); 171 | } 172 | } 173 | 174 | QPainterPath WaterFloatButton::getBgPainterPath() 175 | { 176 | QPainterPath path1, path2, path3; 177 | int w = size().width(), h = size().height(); 178 | 179 | QRect mrect(w/2-mwidth/2, h/2-radius, mwidth, radius*2); 180 | path1.addRect(mrect); 181 | 182 | QPoint o1(w/2-mwidth/2, h/2); 183 | QPoint o2(w/2+mwidth/2, h/2); 184 | path2.addEllipse(o1.x()-radius, o1.y()-radius, radius*2, radius*2); 185 | path3.addEllipse(o2.x()-radius, o2.y()-radius, radius*2, radius*2); 186 | 187 | return path1 | path2 | path3; 188 | } 189 | 190 | QPainterPath WaterFloatButton::getWaterPainterPath(InteractiveButtonBase::Water water) 191 | { 192 | QPainterPath path = InteractiveButtonBase::getWaterPainterPath(water) & getBgPainterPath(); 193 | return path; 194 | } 195 | 196 | bool WaterFloatButton::inArea(QPoint point) 197 | { 198 | int w = size().width(), h = size().height(); 199 | QPoint o1(w/2-mwidth/2, h/2); 200 | QPoint o2(w/2+mwidth/2, h/2); 201 | QRect mrect(w/2-mwidth/2, h/2-radius, mwidth, radius*2); 202 | 203 | if (mrect.contains(point)) 204 | return true; 205 | if ((point-o1).manhattanLength() <= radius || 206 | (point-o2).manhattanLength() <= radius) 207 | return true; 208 | return false; 209 | } 210 | -------------------------------------------------------------------------------- /facile_menu/facilemenu.h: -------------------------------------------------------------------------------- 1 | #ifndef FACILEMENU_H 2 | #define FACILEMENU_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 "facilemenuitem.h" 16 | #include "facilemenubarinterface.h" 17 | 18 | #define DEFAULT_MENU_BLUR_ALPHA 33 19 | 20 | #define newFacileMenu FacileMenu *menu = (new FacileMenu(this)) // 加上括号是为了可以直接设置属性 21 | 22 | typedef std::function const FuncCheckType; 23 | 24 | class FacileMenu : public QWidget 25 | { 26 | Q_OBJECT 27 | public: 28 | FacileMenu(QWidget *parent = nullptr); 29 | ~FacileMenu() override; 30 | 31 | FacileMenuItem* addAction(QIcon icon, QString text, FuncType clicked = nullptr); 32 | FacileMenuItem* addAction(QIcon icon, FuncType clicked = nullptr); 33 | FacileMenuItem* addAction(QString text, FuncType clicked = nullptr); 34 | FacileMenuItem* addAction(QAction* action, bool deleteWithMenu = false); 35 | template 36 | FacileMenuItem* addAction(QIcon icon, QString text, T *obj, void (T::*func)()); 37 | FacileMenu* addNumberedActions(QString pattern, int numberStart, int numberEnd, FuncItemType config = nullptr, FuncIntType clicked = nullptr, int step = 0); 38 | FacileMenu* addNumberedActions(QString pattern, int numberStart, int numberEnd, FuncItemIntType config, FuncIntType clicked = nullptr, int step = 0); 39 | FacileMenu* addActions(QList actions); 40 | 41 | FacileMenu* addRow(FuncType addActions = []{}); 42 | FacileMenu* beginRow(); 43 | FacileMenu* endRow(); 44 | QVBoxLayout* createNextColumn(); 45 | QBoxLayout* currentLayout() const; 46 | FacileMenu* addTitle(QString text, int split = 0); 47 | 48 | FacileMenu* addMenu(QIcon icon, QString text, FuncType clicked = nullptr); 49 | FacileMenu* addMenu(QString text, FuncType clicked = nullptr); 50 | FacileMenu* addMenu(QMenu* menu); 51 | FacileMenuItem* parentAction(); 52 | FacileMenuItem* lastAction(); 53 | FacileMenuItem* currentAction(); 54 | 55 | FacileMenu* addLayout(QLayout *layout, int stretch = 0); 56 | FacileMenu* addLayoutItem(QLayoutItem *item); 57 | FacileMenu* addSpacerItem(QSpacerItem *spacerItem); 58 | FacileMenu* addSpacing(int size); 59 | FacileMenu* addStretch(int stretch = 0); 60 | FacileMenu* addStrut(int size); 61 | FacileMenu* addWidget(QWidget *widget, int stretch = 0, Qt::Alignment alignment = Qt::Alignment()); 62 | FacileMenu* setSpacing(int spacing); 63 | FacileMenu* setStretchFactor(QWidget *widget, int stretch); 64 | FacileMenu* setStretchFactor(QLayout *layout, int stretch); 65 | 66 | FacileMenuItem* addSeparator(); 67 | FacileMenu* split(); 68 | FacileMenuItem* lastAddedItem(); 69 | bool hasFocus() const; 70 | 71 | int indexOf(FacileMenuItem* item); 72 | FacileMenuItem* at(int index); 73 | void setMenuBar(FacileMenuBarInterface* mb); 74 | 75 | void exec(QPoint pos = QPoint(-1, -1)); 76 | void exec(QRect expt, bool vertical = false, QPoint pos = QPoint(-1, -1)); 77 | void execute(); 78 | void getBackgroupPixmap(); 79 | void toHide(int focusIndex = -1); 80 | void toClose(); 81 | bool isClosedByClick() const; 82 | FacileMenu* finished(FuncType func); 83 | 84 | FacileMenu* addOptions(QListtexts, QListstates, FuncIntType clicked); 85 | FacileMenu* addOptions(QListtexts, int select, FuncIntType clicked); 86 | FacileMenu* singleCheck(FacileMenuItem* item); 87 | FacileMenu* uncheckAll(FacileMenuItem* except = nullptr, int begin = -1, int end = -1); 88 | QList checkedItems(); 89 | QList checkedIndexes(); 90 | QStringList checkedItemTexts(); 91 | QList checkedItemDatas(); 92 | FacileMenu* setSingleCheck(FuncCheckType clicked = nullptr); 93 | FacileMenu* setMultiCheck(FuncCheckType clicked = nullptr); 94 | 95 | FacileMenu* setTipArea(int x = 48); 96 | FacileMenu* setTipArea(QString longestTip); 97 | FacileMenu* setSplitInRow(bool split = true); 98 | FacileMenu* setBorderRadius(int r); 99 | FacileMenu* setItemsHoverColor(QColor c); 100 | FacileMenu* setItemsPressColor(QColor c); 101 | FacileMenu* setItemsTextColor(QColor c); 102 | 103 | FacileMenu* setAppearAnimation(bool en); 104 | FacileMenu* setDisappearAnimation(bool en); 105 | FacileMenu* setSubMenuShowOnCursor(bool en); 106 | 107 | signals: 108 | void signalActionTriggered(FacileMenuItem* action); 109 | void signalDynamicMenuTriggered(FacileMenuItem* item); 110 | void signalHidden(); // 只是隐藏了自己 111 | 112 | private slots: 113 | void itemMouseEntered(FacileMenuItem* item); 114 | 115 | protected: 116 | FacileMenu(bool sub, QWidget* parent = nullptr); 117 | FacileMenuItem* createMenuItem(QIcon icon, QString text); 118 | Qt::Key getShortcutByText(QString text) const; 119 | void setActionButton(InteractiveButtonBase* btn, bool isChip = false); 120 | void showSubMenu(FacileMenuItem* item); 121 | bool isCursorInArea(QPoint pos, FacileMenu* child = nullptr); 122 | void setKeyBoardUsed(bool use = true); 123 | bool isSubMenu() const; 124 | FacileMenuItem* addVSeparator(); 125 | void startAnimationOnShowed(); 126 | void startAnimationOnHidden(int focusIndex); 127 | 128 | void showEvent(QShowEvent *event) override; 129 | void hideEvent(QHideEvent *event) override; 130 | void mouseMoveEvent(QMouseEvent *event) override; 131 | void keyPressEvent(QKeyEvent *event) override; 132 | void paintEvent(QPaintEvent *event) override; 133 | 134 | public: 135 | static void setColors(QColor normal, QColor hover, QColor press, QColor text); 136 | static QColor normal_bg; // 普通背景(用作全局是为了方便设置) 137 | static QColor hover_bg; // 悬浮背景 138 | static QColor press_bg; // 按下背景 139 | static QColor text_fg; // 字体/变色图标颜色 140 | static int blur_bg_alpha; // 背景图显示程度,0禁用,1~100为模糊透明度 141 | static QEasingCurve easing_curve; // 出现的动画曲线 142 | static bool auto_dark_mode; // 是否自动根据系统设置调整颜色 143 | static bool auto_theme_by_bg; // 是否根据背景颜色自动调整主题 144 | static bool all_menu_same_color; // 所有菜单统一颜色(子菜单可以是不同颜色) 145 | 146 | private: 147 | QList items; 148 | QList v_separators, h_separators; 149 | QList other_widgets; // 手动添加的widget 150 | QHBoxLayout* main_hlayout; 151 | QVBoxLayout* main_vlayout; 152 | QList row_hlayouts; 153 | QList import_actions; 154 | QPixmap bg_pixmap; 155 | 156 | FacileMenu* current_sub_menu = nullptr; // 当前打开(不一定显示)的子菜单 157 | FacileMenu* parent_menu = nullptr; // 父对象的菜单 158 | FacileMenuItem* last_added_item = nullptr; // 最后添加的item 159 | FacileMenuBarInterface* menu_bar = nullptr; // 菜单栏接口 160 | FuncType* finished_func = nullptr; // 析构前要执行的 161 | 162 | bool hidden_by_another = false; // 是否是被要显示的另一个子菜单替换了。若否,隐藏全部菜单 163 | const int item_padding = 8; // 每个item四周的空白 164 | const int tip_area_spacing = 8; // item正文和tip的间距 165 | bool adding_horizone = false; // 是否正在添加横向菜单 166 | bool align_mid_if_alone = false; // 是否居中对齐,如果只有icon或text 167 | bool linger_on_submenu_clicked = false; // 子菜单点击后,父菜单是否逐级隐藏(注意子菜单若有选择项需手动改变) 168 | bool _showing_animation = false; 169 | int current_index = -1; // 当前索引 170 | bool using_keyboard = false; // 是否正在使用键盘挑选菜单 171 | QRect window_rect; 172 | int window_height = 0; // 窗口高度,每次打开都更新一次 173 | QPoint _enter_later_pos = QPoint(-1, -1); // 避免连续两次触发 enterLater 事件 174 | bool closed_by_clicked = false; // 是否因为被单击了才隐藏,还是因为其他原因关闭 175 | 176 | // 可修改的配置属性 177 | int addin_tip_area = 48; // 右边用来显示提示文字的区域 178 | int border_radius = 5; // 圆角 179 | bool split_in_row = false; // 同一行是否默认添加分割线 180 | bool enable_appear_animation = true; 181 | bool enable_disappear_animation = true; 182 | bool sub_menu_show_on_cursor = true; // 子菜单跟随鼠标出现还是在主菜单边缘 183 | 184 | // 一些动画优化 185 | QTimer* frame_timer = nullptr; 186 | QColor m_bg_color; 187 | QColor m_text_color; 188 | }; 189 | 190 | #endif // FACILEMENU_H 191 | -------------------------------------------------------------------------------- /interactive_buttons/waterfallbuttongroup.cpp: -------------------------------------------------------------------------------- 1 | #include "waterfallbuttongroup.h" 2 | #include 3 | 4 | WaterFallButtonGroup::WaterFallButtonGroup(QWidget *parent) 5 | : QWidget(parent), 6 | normal_bg(128,128,128,32), 7 | hover_bg(100,149,237,128), 8 | press_bg(100,149,237), 9 | selected_bg(100,149,237,128), 10 | normal_ft(0,0,0), 11 | selected_ft(255,255,255) 12 | { 13 | 14 | } 15 | 16 | void WaterFallButtonGroup::initStringList(QStringList list, QStringList selected) 17 | { 18 | foreach (QString s, list) 19 | { 20 | addButton(s, selected.contains(s)); 21 | } 22 | } 23 | 24 | /** 25 | * 选择指定项 26 | * 会清除已有的选择 27 | */ 28 | void WaterFallButtonGroup::setSelects(QStringList list) 29 | { 30 | selectable = true; 31 | foreach (InteractiveButtonBase *btn, btns) 32 | { 33 | if (btn->getText().isEmpty()) 34 | continue; 35 | if (list.contains(btn->getText()) && !btn->getState()) 36 | changeBtnSelect(btn); 37 | else if (!list.contains(btn->getText()) && btn->getState()) 38 | changeBtnSelect(btn); 39 | } 40 | } 41 | 42 | void WaterFallButtonGroup::setSelect(QString text) 43 | { 44 | foreach (InteractiveButtonBase *btn, btns) 45 | { 46 | if (btn->getText() == text) 47 | { 48 | changeBtnSelect(btn); 49 | break; 50 | } 51 | } 52 | } 53 | 54 | void WaterFallButtonGroup::setSelect(int index) 55 | { 56 | if (index < 0 || index >= btns.size()) 57 | return; 58 | 59 | changeBtnSelect(btns.at(index)); 60 | } 61 | 62 | WaterFloatButton* WaterFallButtonGroup::addButton(QString s, bool selected) 63 | { 64 | return addButton(s, QColor(), selected); 65 | } 66 | 67 | WaterFloatButton* WaterFallButtonGroup::addButton(QString s, QColor c, bool selected) 68 | { 69 | WaterFloatButton* btn = new WaterFloatButton(s, this); 70 | btn->setFixedForeSize(); 71 | setBtnColors(btn); 72 | btn->show(); 73 | btns.append(btn); 74 | 75 | if (selected) 76 | changeBtnSelect(btn); 77 | 78 | btn->setAutoTextColor(false); 79 | if (c.isValid()) 80 | btn->setTextColor(c); 81 | connect(btn, &InteractiveButtonBase::clicked, this, [=]{ 82 | slotButtonClicked(btn); 83 | }); 84 | 85 | btn->setContextMenuPolicy(Qt::CustomContextMenu); 86 | connect(btn, &InteractiveButtonBase::customContextMenuRequested, this, [=](QPoint){ 87 | emit signalRightClicked(s); 88 | }); 89 | /* connect(btn, &InteractiveButtonBase::rightClicked, this, [=]{ 90 | emit signalRightClicked(s); 91 | }); */ 92 | } 93 | 94 | void WaterFallButtonGroup::slotButtonClicked(InteractiveButtonBase* btn) 95 | { 96 | QString s = btn->getText(); 97 | if (selectable) 98 | { 99 | if (single_select) // 单选,只选择它 100 | { 101 | if (btn->getState()) // 已经选择了,则不进行处理 102 | return; 103 | 104 | // 取消所有选择 105 | foreach (auto btn, btns) 106 | { 107 | if (btn->getState()) 108 | unselectBtn(btn); 109 | } 110 | 111 | // 只选择它 112 | selectBtn(btn); 113 | } 114 | else // 多选,那么可以取消选择 115 | { 116 | changeBtnSelect(btn); 117 | } 118 | 119 | if (btn->getState()) 120 | emit signalSelected(s); 121 | else 122 | emit signalUnselected(s); 123 | emit signalSelectChanged(); 124 | } 125 | else 126 | { 127 | emit signalClicked(s); 128 | } 129 | } 130 | 131 | QStringList WaterFallButtonGroup::getSelectedTexts() const 132 | { 133 | QStringList results; 134 | for (int i = 0; i < btns.size(); i++) 135 | { 136 | if (btns.at(i)->getState()) 137 | results.append(btns.at(i)->getText()); 138 | } 139 | return results; 140 | } 141 | 142 | QList WaterFallButtonGroup::getSelectedIndexes() const 143 | { 144 | QList results; 145 | for (int i = 0; i < btns.size(); i++) 146 | { 147 | if (btns.at(i)->getState()) 148 | results.append(i); 149 | } 150 | return results; 151 | } 152 | 153 | void WaterFallButtonGroup::clear() 154 | { 155 | for (int i = 0; i < btns.size(); i++) 156 | { 157 | btns.at(i)->deleteLater(); 158 | } 159 | btns.clear(); 160 | } 161 | 162 | void WaterFallButtonGroup::setColors(QColor normal_bg, QColor hover_bg, QColor press_bg, QColor selected_bg, QColor normal_ft, QColor selected_ft) 163 | { 164 | this->normal_bg = normal_bg; 165 | this->hover_bg = hover_bg; 166 | this->press_bg = press_bg; 167 | this->selected_bg = selected_bg; 168 | this->normal_ft = normal_ft; 169 | if (selected_ft != Qt::transparent) 170 | this->selected_ft = selected_ft; 171 | else 172 | selected_ft = getReverseColor(selected_bg); 173 | } 174 | 175 | void WaterFallButtonGroup::setSelectedColor(QColor color) 176 | { 177 | this->selected_bg = color; 178 | } 179 | 180 | void WaterFallButtonGroup::updateBtnColors() 181 | { 182 | foreach (InteractiveButtonBase *btn, btns) 183 | { 184 | setBtnColors(btn); 185 | } 186 | } 187 | 188 | int WaterFallButtonGroup::count() const 189 | { 190 | return btns.size(); 191 | } 192 | 193 | QList WaterFallButtonGroup::getButtons() const 194 | { 195 | return btns; 196 | } 197 | 198 | void WaterFallButtonGroup::setSelectable(bool enable) 199 | { 200 | this->selectable = enable; 201 | } 202 | 203 | void WaterFallButtonGroup::setSingleSelect(bool enable) 204 | { 205 | this->single_select = enable; 206 | setSelectable(true); 207 | } 208 | 209 | void WaterFallButtonGroup::updateButtonPositions() 210 | { 211 | const int space_h = 3, space_v = 3; 212 | int total_w = this->width(), w = space_v; 213 | int total_h = 0; 214 | // 自动调整位置 215 | foreach (InteractiveButtonBase* btn, btns) 216 | { 217 | int btn_w = btn->width(); 218 | if (w == 0 || w + btn_w <= total_w) // 开头,或者同一行 219 | { 220 | btn->move(w, total_h); 221 | w += btn_w + space_h; 222 | } 223 | else // 另起一行 224 | { 225 | total_h += btn->height() + space_v; 226 | w = space_h; 227 | btn->move(w, total_h); 228 | w += btn_w + space_h; 229 | } 230 | } 231 | if (btns.size()) 232 | total_h += btns.last()->height(); 233 | this->setFixedHeight(total_h); 234 | } 235 | 236 | void WaterFallButtonGroup::resizeEvent(QResizeEvent *event) 237 | { 238 | updateButtonPositions(); 239 | 240 | QWidget::resizeEvent(event); 241 | } 242 | 243 | void WaterFallButtonGroup::paintEvent(QPaintEvent *event) 244 | { 245 | QStyleOption opt; 246 | opt.init(this); 247 | QPainter p(this); 248 | style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); 249 | } 250 | 251 | QColor WaterFallButtonGroup::getReverseColor(QColor color) 252 | { 253 | return QColor( 254 | getReverseChannel(color.red()), 255 | getReverseChannel(color.green()), 256 | getReverseChannel(color.blue()) 257 | ); 258 | } 259 | 260 | int WaterFallButtonGroup::getReverseChannel(int x) 261 | { 262 | if (x < 92 || x > 159) 263 | return 255 - x; 264 | else if (x < 128) 265 | return 255; 266 | else // if (x > 128) 267 | return 0; 268 | } 269 | 270 | void WaterFallButtonGroup::setBtnColors(InteractiveButtonBase *btn) 271 | { 272 | btn->setBgColor(normal_bg); 273 | btn->setBgColor(hover_bg, press_bg); 274 | } 275 | 276 | void WaterFallButtonGroup::changeBtnSelect(InteractiveButtonBase *btn) 277 | { 278 | if (btn->getState()) // 已选中,取消选中 279 | { 280 | unselectBtn(btn); 281 | } 282 | else // 未选中,进行选中 283 | { 284 | selectBtn(btn); 285 | } 286 | } 287 | 288 | void WaterFallButtonGroup::selectBtn(InteractiveButtonBase *btn) 289 | { 290 | btn->setState(true); 291 | btn->setBgColor(selected_bg); 292 | } 293 | 294 | void WaterFallButtonGroup::unselectBtn(InteractiveButtonBase *btn) 295 | { 296 | btn->setState(false); 297 | btn->setBgColor(normal_bg); 298 | } 299 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /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 | // 设置菜单栏 11 | FacileMenu* fileMenu = new FacileMenu(this); 12 | fileMenu->addAction("新建"); 13 | fileMenu->addAction("打开"); 14 | fileMenu->addAction("保存"); 15 | fileMenu->addAction("另存为"); 16 | fileMenu->split()->addAction("退出"); 17 | 18 | FacileMenu* editMenu = new FacileMenu(this); 19 | editMenu->addAction("撤销"); 20 | editMenu->addAction("重做"); 21 | editMenu->split()->addAction("查找"); 22 | editMenu->addAction("替换"); 23 | FacileMenu* m = editMenu->split()->addMenu("编码"); 24 | m->addAction("ANSI"); 25 | m->addAction("UTF-8"); 26 | m->addAction("GBK"); 27 | 28 | FacileMenu* formatMenu = new FacileMenu(this); 29 | formatMenu->addAction("自动换行"); 30 | 31 | FacileMenu* viewMenu = new FacileMenu(this); 32 | m = viewMenu->addMenu("缩放"); 33 | m->addAction("放大"); 34 | m->addAction("缩小"); 35 | m->addAction("回复默认"); 36 | viewMenu->addAction("全屏"); 37 | 38 | FacileMenu* helpMenu = new FacileMenu(this); 39 | helpMenu->addAction("帮助"); 40 | helpMenu->addAction("反馈"); 41 | helpMenu->split()->addAction("关于"); 42 | 43 | ui->menuBar->setAnimationEnabled(false); // 开启动画。有闪烁的问题,受得了就能开 44 | ui->menuBar->addMenu("文件", fileMenu); 45 | ui->menuBar->addMenu("编辑", editMenu); 46 | ui->menuBar->addMenu("查看", viewMenu); 47 | ui->menuBar->addMenu("帮助", helpMenu); 48 | ui->menuBar->insertMenu(2, "格式", formatMenu); 49 | } 50 | 51 | MainWindow::~MainWindow() 52 | { 53 | delete ui; 54 | } 55 | 56 | void MainWindow::on_pushButton_clicked() 57 | { 58 | static bool faster_checked = true; 59 | static bool slower_checked = false; 60 | 61 | FacileMenu* menu = (new FacileMenu(this)) 62 | ->setTipArea("Ctrl+P") 63 | //->setSubMenuShowOnCursor(false) 64 | ->setSplitInRow(true); 65 | 66 | menu->addAction(QIcon(":/icons/run"), "开始播放 (&S)", [=]{ 67 | qDebug() << "=>开始播放"; 68 | })->tip("Ctrl+S"); 69 | 70 | menu->addAction(QIcon(":/icons/pause"), "暂停 (&P)", [=]{ 71 | qDebug() << "=>暂停"; 72 | })->tip("Ctrl+P")->suffix(true, "☆"); 73 | 74 | menu->addAction(QIcon(":/icons/resume"), "继续 (&R)", [=]{ 75 | qDebug() << "=>继续"; 76 | })->disable()->tip("Ctrl+R"); 77 | 78 | menu->addAction(QIcon(":/icons/stop"), "停止 (&T)", [=]{ 79 | qDebug() << "=>停止"; 80 | })->tip("Ctrl+T"); 81 | menu->addSeparator(); 82 | 83 | menu->addAction(QIcon(":/icons/faster"), "加速", [=]{ 84 | qDebug() << "=>加速"; 85 | faster_checked = !faster_checked; 86 | })->check(faster_checked)->linger() 87 | ->ifer(false) 88 | ->prefix("1") 89 | ->elser() 90 | ->prefix("2"); 91 | 92 | int number = 3; 93 | menu->addAction(QIcon(":/icons/slower"), "减速", [=]{ 94 | qDebug() << "=>减速"; 95 | slower_checked = !slower_checked; 96 | })->check(slower_checked)->linger() 97 | ->switcher(number) 98 | ->caser(1) 99 | ->suffix("1") 100 | ->breaker() 101 | ->caser(2) 102 | ->suffix("2") 103 | // ->breaker() 104 | ->caser(3) 105 | ->suffix(" caser") 106 | ->breaker() 107 | ->caser(4) 108 | ->suffix("4") 109 | ->breaker() 110 | ->caser(6, [=]{ qDebug() << "(^_−)☆"; }) 111 | ->defaulter() 112 | ->suffix("5") 113 | ->breaker(); 114 | 115 | menu->addRow([=]{ 116 | menu->addAction("按钮1"); 117 | menu->addAction("按钮2")->borderR(); 118 | menu->addAction("按钮3"); 119 | }); 120 | 121 | auto subMenu = menu->addMenu("子菜单0"); 122 | { 123 | subMenu->addAction(QIcon(":/icons/run"), "开始播放", [=]{ 124 | qDebug() << "=>开始播放"; 125 | }); 126 | 127 | subMenu->addAction(QIcon(":/icons/pause"), "暂停", [=]{ 128 | qDebug() << "=>暂停"; 129 | }); 130 | 131 | subMenu->addAction(QIcon(":/icons/resume"), "继续", [=]{ 132 | qDebug() << "=>继续"; 133 | })->disable(); 134 | 135 | subMenu->addAction(QIcon(":/icons/stop"), "停止", [=]{ 136 | qDebug() << "=>停止"; 137 | }); 138 | 139 | subMenu->addAction(QIcon(":/icons/faster"), "加速", [=]{ 140 | qDebug() << "=>加速"; 141 | faster_checked = !faster_checked; 142 | })->setChecked(faster_checked); 143 | 144 | auto subMenu2 = subMenu->addMenu("子菜单"); 145 | { 146 | subMenu2->addAction(QIcon(":/icons/run"), "开始播放", [=]{ 147 | qDebug() << "=>开始播放"; 148 | }); 149 | 150 | subMenu2->addAction(QIcon(":/icons/pause"), "暂停", [=]{ 151 | qDebug() << "=>暂停"; 152 | }); 153 | 154 | subMenu2->addAction(QIcon(":/icons/resume"), "继续", [=]{ 155 | qDebug() << "=>继续"; 156 | })->disable(); 157 | 158 | subMenu2->addAction(QIcon(":/icons/stop"), "停止", [=]{ 159 | qDebug() << "=>停止"; 160 | }); 161 | 162 | subMenu2->addAction(QIcon(":/icons/faster"), "加速", [=]{ 163 | qDebug() << "=>加速"; 164 | faster_checked = !faster_checked; 165 | })->setChecked(faster_checked); 166 | 167 | auto subMenu3 = subMenu2->addMenu("子菜单"); 168 | subMenu3->addAction("没有子菜单了"); 169 | } 170 | } 171 | 172 | auto subMenu2 = menu->addMenu("单选菜单"); 173 | { 174 | auto ac1 = subMenu2->addAction(QIcon(":/icons/run"), "带图标")->check()->linger(); 175 | auto ac2 = subMenu2->addAction("无图标")->uncheck()->linger(); 176 | auto ac3 = subMenu2->split()->addAction("全不选")->uncheck()->linger(); 177 | 178 | // 连接点击事件 179 | ac1->triggered([=]{ 180 | subMenu2->singleCheck(ac1); // 用于单选,表示只选中ac1 181 | // 这里可以用于处理其他操作 182 | }); 183 | ac2->triggered([=]{ 184 | subMenu2->singleCheck(ac2); 185 | }); 186 | ac3->triggered([=]{ 187 | subMenu2->uncheckAll(); // 全不选 188 | }); 189 | } 190 | 191 | auto subMenu4 = menu->addMenu("快速批量单选项"); 192 | { 193 | QStringList texts; 194 | QList values; 195 | for (int i = 0; i < 10; i++) 196 | { 197 | int val = qrand() % 1000; 198 | texts << "项目"+QString::number(val); 199 | values << val; 200 | } 201 | static int selected = 2; 202 | 203 | subMenu4->addOptions(texts, selected, [=](int index){ 204 | qDebug() << "选中了:" << (selected = index) << ",值:" << values.at(index); 205 | }); 206 | // 这里不建议(也没有必要)修改checked状态,因为点了就隐藏掉了 207 | } 208 | 209 | auto subMenu5 = menu->addMenu("多选菜单"); 210 | { 211 | // 假装是某一个需要多选的属性 212 | QList* list = new QList(); 213 | 214 | for (int i = 0; i < 10; i++) 215 | { 216 | int val = qrand() % 1000; // 放入菜单项的自定义数据 217 | auto action = subMenu5->addAction("选项"+QString::number(val))->setData(val)->uncheck()->autoToggle()->linger(); 218 | action->triggered([=]{ 219 | // 自己的处理流程,例如调用某个外部的方法 220 | if (action->isChecked()) 221 | list->append(action->getData().toInt()); 222 | else 223 | list->removeOne(action->getData().toInt()); 224 | qDebug() << "当前选中的有:" << *list; 225 | }); 226 | } 227 | 228 | subMenu5->finished([=]{ 229 | if (list->size()) 230 | qDebug() << "多选 最终选中:" << *list; 231 | delete list; 232 | }); 233 | } 234 | 235 | auto subMenu6 = menu->addMenu("快速批量多选项"); 236 | { 237 | // 假装是某一个需要多选的属性 238 | QList* list = new QList(); 239 | subMenu6->addNumberedActions("选项%1", 0, 10) 240 | ->setMultiCheck([=](int index, bool checked){ 241 | if (checked) 242 | list->append(index); 243 | else 244 | list->removeOne(index); 245 | qDebug() << "当前选中的有:" << *list; 246 | }); 247 | } 248 | 249 | auto subMenu7 = menu->addMenu("极简批量多选项"); 250 | { 251 | QList* list = new QList(); 252 | subMenu7->addNumberedActions("选项%1", 0, 15)->setMultiCheck() 253 | ->finished([=]{ 254 | *list = subMenu7->checkedItemTexts(); 255 | if (list->size()) 256 | qDebug() << "极简多选项 最终选中:" << *list; 257 | }); 258 | } 259 | 260 | auto subMenu8 = menu->addMenu("批量数字项"); 261 | { 262 | subMenu8->addNumberedActions("选项%1", 3, 13, [&](FacileMenuItem* item, int i){ 263 | item->setChecked(i%5==0)->linger()->autoToggle(); 264 | })->finished([=]{ 265 | auto list = subMenu8->checkedItemTexts(); 266 | if (list.size() != 2) 267 | qDebug() << "批量数字项 最终选中:" << list; 268 | }); 269 | } 270 | 271 | auto subMenu3 = menu->addMenu("判断语句"); 272 | { 273 | static bool b = false; 274 | subMenu3->addAction("if else 判断") 275 | ->tooltip("试一下 ifer elser") 276 | ->text(b, "当前:if", "当前:else") 277 | ->triggered([=]{b = !b;}) 278 | ->ifer(b) 279 | ->check() 280 | ->elser() 281 | ->uncheck(); 282 | } 283 | 284 | // 导入QMenu和QAction 285 | { 286 | QMenu* m = new QMenu("QMenu菜单", this); 287 | QAction* action1 = new QAction("子action1", this); 288 | QAction* action2 = new QAction("子action2", this); 289 | QAction* action3 = new QAction("子action3", this); 290 | m->addAction(action1); 291 | m->addAction(action2); 292 | m->addAction(action3); 293 | QMenu* m2 = new QMenu("QMenu菜单2", this); 294 | QAction* action21 = new QAction("子action21", this); 295 | QAction* action22 = new QAction("子action22", this); 296 | QAction* action23 = new QAction("子action23", this); 297 | m2->addAction(action21); 298 | m2->addAction(action22); 299 | m2->addAction(action23); 300 | action2->setMenu(m2); 301 | connect(action1, &QAction::triggered, this, [=]{ qDebug() << "action1.triggered"; }); 302 | connect(action21, &QAction::triggered, this, [=]{ qDebug() << "action21.triggered"; }); 303 | 304 | menu->addMenu(m); 305 | } 306 | 307 | menu->exec(QCursor::pos()); 308 | } 309 | 310 | void MainWindow::on_pushButton_2_clicked() 311 | { 312 | static bool faster_checked = true; 313 | static bool slower_checked = false; 314 | 315 | FacileMenu* menu = new FacileMenu(this); 316 | menu->setTipArea("Ctrl+P"); 317 | 318 | menu->addRow([=]{ 319 | menu->addAction("按钮1")->disable(); 320 | menu->addAction("按钮2")->disable(); 321 | menu->addAction("按钮3")->text(true, "按钮3 (&K)"); 322 | }); 323 | 324 | menu->addAction(QIcon(":/icons/run"), "开始播放 (&S)", [=]{ 325 | qDebug() << "=>开始播放"; 326 | })->tip("Ctrl+S")->fgColor(Qt::blue); 327 | 328 | menu->addAction(QIcon(":/icons/pause"), "暂停 (&P)", [=]{ 329 | qDebug() << "=>暂停"; 330 | })->tip("Ctrl+P")->bgColor(true, Qt::gray); 331 | 332 | QPushButton* button = new QPushButton("外部添加的按钮", this); 333 | menu->addWidget(button); 334 | 335 | QHBoxLayout* btn_hlayout = new QHBoxLayout; 336 | QPushButton* btn1= new QPushButton("按钮1", this); 337 | QPushButton* btn2 = new QPushButton("按钮2", this); 338 | btn_hlayout->addSpacing(40); 339 | btn_hlayout->addWidget(btn1); 340 | btn_hlayout->addItem(new QSpacerItem(4, 0)); 341 | btn_hlayout->addWidget(btn2); 342 | menu->addLayout(btn_hlayout); 343 | 344 | menu->addAction(QIcon(":/icons/resume"), "继续 (&R)", [=]{ 345 | qDebug() << "=>继续"; 346 | })->disable()->tip("Ctrl+R"); 347 | 348 | menu->addAction(QIcon(":/icons/stop"), "停止 (&T)", [=]{ 349 | qDebug() << "=>停止"; 350 | })->tip("Ctrl+T"); 351 | menu->addSeparator(); 352 | 353 | menu->addAction(QIcon(), "静态回调方法", staticFunction); 354 | // menu->fun2(1, "类内回调方法", this, &MainWindow::classFunction); 355 | 356 | menu->addAction(QIcon(":/icons/faster"), "加速", [=]{ 357 | qDebug() << "=>加速"; 358 | faster_checked = !faster_checked; 359 | })->setChecked(faster_checked); 360 | 361 | menu->addAction(QIcon(":/icons/slower"), "减速", [=]{ 362 | qDebug() << "=>减速"; 363 | slower_checked = !slower_checked; 364 | })->setChecked(slower_checked); 365 | 366 | menu->beginRow(); 367 | menu->addAction(QIcon(":/icons/run")); 368 | menu->addAction(QIcon(":/icons/pause")); 369 | menu->split()->addAction(QIcon(":/icons/resume")); 370 | menu->addAction(QIcon(":/icons/stop"))->disable(); 371 | menu->endRow(); 372 | 373 | menu->addAction(QIcon(":/icons/faster"), "加速", [=]{ 374 | qDebug() << "=>加速"; 375 | faster_checked = !faster_checked; 376 | })->setChecked(faster_checked)->disable(); 377 | 378 | menu->addAction(QIcon(":/icons/slower"), "减速", [=]{ 379 | qDebug() << "=>减速"; 380 | slower_checked = !slower_checked; 381 | })->setChecked(slower_checked)->disable(); 382 | 383 | menu->beginRow(); 384 | menu->addAction("按钮1"); 385 | menu->addAction("按钮2")->disable(); 386 | menu->addAction("按钮3")->disable(); 387 | menu->endRow(); 388 | 389 | menu->exec(QRect(mapToGlobal(ui->pushButton_2->pos()), ui->pushButton_2->size()), true, QCursor::pos()); 390 | } 391 | 392 | void MainWindow::staticFunction() 393 | { 394 | qDebug() << "静态方法回调"; 395 | } 396 | 397 | void MainWindow::classFunction() 398 | { 399 | qDebug() << "类内方法回调"; 400 | } 401 | 402 | void MainWindow::on_pushButton_3_clicked() 403 | { 404 | 405 | FacileMenu* menu = (new FacileMenu(this)); 406 | 407 | for (int i = 0; i < 80; i++) 408 | { 409 | menu->addAction("这是一个菜单" + QString::number(i)); 410 | } 411 | 412 | menu->exec(QCursor::pos()); 413 | } 414 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FacileMenu 2 | ==== 3 | 4 | ## 介绍 5 | 6 | 非常飘逸的 Qt 菜单控件,带有各种动画效果,用起来也十分方便。 7 | 8 | 无限层级,响应键盘、鼠标单独操作,自动切换日夜间模式或主题,支持单快捷键。 9 | 10 | 允许添加自定义 widget、layout,当做特殊的 QDialog 使用。 11 | 12 | 13 | 14 | ## 简单使用 15 | 16 | 1. 放入源代码 17 | 将 `facile_menu` 文件夹放入 Qt 程序,pro 文件的 `INCLUDEPATH` 加上对应路径,`resources` 里的资源文件 `sub_menu_arrow.png` (子菜单箭头)也导入,前缀别名为:`:/icons/sub_menu_arrow`(或按需修改);同理添加建议的色彩资源 `:/documents/color_list` 18 | 19 | 2. `.pro` 文件需要添加 `QT += core gui svg` 20 | 21 | 3. 包含头文件 `#include "facile_menu.h"` 22 | 23 | 4. 创建并显示菜单 24 | 25 | ```C++ 26 | // 创建菜单 27 | FacileMenu* menu = new FacileMenu(this); 28 | 29 | // 添加动作 30 | menu->addAction(QIcon(":/icons/run"), "开始播放 (&S)", [=]{ 31 | /* 某操作 */ 32 | })->tip("Ctrl+S")->disable()->hide(); 33 | 34 | // 显示菜单 35 | menu->execute(QCursor::pos()); 36 | ``` 37 | 38 | 39 | 40 | ## 常用操作 41 | 42 | ### 连续设置 43 | 44 | ```C++ 45 | menu->addAction(QIcon(":/icons/run"), "开始播放 (&S)", [=]{}) 46 | ->tip("Ctrl+S") 47 | ->disable(playing/*如果满足某条件(默认true)则disable,不满足跳过,下同*/) 48 | ->hide(/*条件表达式*/) 49 | ->uncheck(false); 50 | ``` 51 | 52 | 53 | 54 | ### 子菜单 55 | 56 | ```C++ 57 | auto subMenu = menu->addMenu("子菜单2"); 58 | 59 | subMenu->addAction("继续", [=]{}); 60 | 61 | subMenu3->addAction("停止", [=]{}) 62 | ->disable(!playing); 63 | ``` 64 | 65 | 66 | 67 | ### 横向菜单 68 | 69 | 方式一:一口气添加 70 | 71 | ```C++ 72 | menu->addRow([=]{ 73 | menu->addAction("按钮1"); 74 | menu->addAction("按钮2"); 75 | menu->addAction("按钮3"); 76 | }); 77 | ``` 78 | 79 | 方式二:逐个添加 80 | 81 | ```C++ 82 | menu->beginRow(); 83 | menu->addAction(QIcon(":/icons/run")); 84 | menu->addAction(QIcon(":/icons/pause")); 85 | menu->split(); 86 | menu->addAction(QIcon(":/icons/resume")); 87 | menu->addAction(QIcon(":/icons/stop"))->disable(); 88 | menu->endRow(); 89 | ``` 90 | 91 | 两种方式都支持横向布局 widget 92 | 93 | 94 | 95 | ### 添加标题 96 | 97 | ```C++ 98 | menu->addTitle("标题", -1/0/1); 99 | ``` 100 | 101 | 一个灰色文字的 QLabel,根据参数二会选择性添加一条分割线。 102 | 103 | `-1` 添加到标题上方(margin=4),`0` 不添加分割线,`1` 添加到标题下方。默认为 `0`,不带分割线。 104 | 105 | 106 | 107 | ### 添加 QAction 108 | 109 | 支持在菜单关闭时自动 delete 传入的 action,避免内存泄漏(默认关闭) 110 | 111 | ```C++ 112 | QAction* action = ...; 113 | menu->addAction(action, true/*是否在菜单关闭时一起delete*/); 114 | ``` 115 | 116 | 117 | 118 | ### 添加 Widget/Layout 119 | 120 | 添加任意 widget 至菜单中,和菜单项并存,不占 `at(index)/indexOf(item)` 的位置。layout 同理。 121 | 122 | ```C++ 123 | QPushButton* button = new QPushButton("外部添加的按钮", this); 124 | menu->addWidget(button); 125 | ``` 126 | 127 | 128 | 129 | ### 添加单选菜单 130 | 131 | > 如果要设置为checkable,请在创建时调用一次其以下任一方法: 132 | > 133 | > setCheckable(bool) / setChecked(bool) / check(bool) / uncheck(bool) 134 | 135 | ```C++ 136 | // 使用 linger() 使菜单点击后不隐藏,持续显示当前单选/多选结果 137 | auto ac1 = subMenu2->addAction(QIcon(":/icons/run"), "带图标")->check()->linger(); 138 | auto ac2 = subMenu2->addAction("无图标")->uncheck()->linger(); 139 | auto ac3 = subMenu2->split()->addAction("全不选")->uncheck()->linger(); 140 | 141 | // 连接点击事件 142 | ac1->triggered([=]{ 143 | subMenu2->singleCheck(ac1); // 用于单选,表示只选中ac1 144 | // 这里可以用于处理其他操作 145 | }); 146 | ac2->triggered([=]{ 147 | subMenu2->singleCheck(ac2); 148 | }); 149 | ac3->triggered([=]{ 150 | subMenu2->uncheckAll(); // 全不选 151 | }); 152 | ``` 153 | 154 | 155 | 156 | ### 添加多选菜单 157 | 158 | ```C++ 159 | // 假装是某一个需要多选的属性 160 | QList* list = new QList(); 161 | 162 | for (int i = 0; i < 10; i++) 163 | { 164 | auto action = subMenu5->addAction("选项"+QString::number(i))->uncheck()->linger()->autoToggle()/*点击自动切换选中状态*/; 165 | action->triggered([=]{ 166 | // 自己的处理流程,例如调用某个外部的方法 167 | if (action->isChecked()) 168 | list->append(action->getText()); 169 | else 170 | list->removeOne(action->getText()); 171 | qDebug() << "当前选中的有:" << *list; 172 | }); 173 | } 174 | ``` 175 | 176 | 177 | 178 | ### 快速批量单选项 179 | 180 | ```C++ 181 | QStringList texts; 182 | for (int i = 0; i < 10; i++) 183 | texts << "项目"+QString::number(i); 184 | static int selected = 2; 185 | 186 | menu->addOptions(texts, selected, [=](int index){ 187 | qDebug() << "选中了:" << (selected = index) << texts.at(index); 188 | }); 189 | ``` 190 | 191 | 192 | 193 | ### 快速批量多选项 194 | 195 | 监听每一项改变的结果 196 | 197 | ```C++ 198 | // 假装是某一个需要多选的属性 199 | QList* list = new QList(); 200 | subMenu6->addNumberedActions("选项%1", 0, 10) 201 | ->setMultiCheck([=](int index, bool checked){ 202 | if (checked) 203 | list->append(index); 204 | else 205 | list->removeOne(index); 206 | qDebug() << "当前选中的有:" << *list; 207 | }); 208 | ``` 209 | 210 | 211 | 212 | ### 极简批量多选项 213 | 214 | 直接读取多选项结果,而不是监听多选项每一项(也可以两者结合) 215 | 216 | 在`finished()`中获取`checkedItems()`,即为选中项 217 | 218 | ```C++ 219 | QList* list = new QList(); 220 | subMenu7->addNumberedActions("选项%1", 0, 15)->setMultiCheck() 221 | ->finished([=]{ 222 | *list = subMenu7->checkedItemTexts(); 223 | qDebug() << "最终选中:" << *list; 224 | }); 225 | ``` 226 | 227 | 228 | 229 | ### 菜单项 API 230 | 231 | `addAction()`后,可直接设置菜单项的一些属性,包括以下: 232 | 233 | > 第一个参数为`bool`类型的,表示**满足此条件才修改设置**,例如: 234 | > 235 | > ```C++ 236 | > bool needHide = false; 237 | > action->hide(needHide); // 不满足隐藏条件,即无视此语句 238 | > ``` 239 | 240 | ```C++ 241 | // 菜单项右边快捷键区域的文字 242 | // 如果要使用,建议用:setTipArea 来额外添加设置右边空白宽度 243 | FacileMenuItem* tip(QString sc); 244 | FacileMenuItem* tip(bool exp, QString sc); 245 | 246 | // 鼠标悬浮提示 247 | FacileMenuItem* tooltip(QString tt); 248 | FacileMenuItem* tooltip(bool exp, QString tt); 249 | 250 | // 触发(单击、回车键)后,参数为 Lambda 表达式 251 | FacileMenuItem* triggered(FuncType func); 252 | FacileMenuItem* triggered(bool exp, FuncType func); 253 | 254 | // 当参数表达式为true时生效,false时忽略,下同 255 | FacileMenuItem* disable(bool exp = true); 256 | FacileMenuItem* enable(bool exp = true); 257 | 258 | FacileMenuItem* hide(bool exp = true); 259 | FacileMenuItem* visible(bool exp = true); 260 | 261 | FacileMenuItem* check(bool exp = true); 262 | FacileMenuItem* uncheck(bool exp = true); 263 | FacileMenuItem* toggle(bool exp = true); 264 | 265 | // 设置data,一般用于单选、多选 266 | FacileMenuItem* setData(QVariant data); 267 | QVariant getData(); 268 | 269 | FacileMenuItem* text(bool exp, QString str); 270 | // 当表达式为true时,设置为tru文字,否则设置为fal文字 271 | FacileMenuItem* text(bool exp, QString tru, QString fal); 272 | 273 | FacileMenuItem* fgColor(QColor color); 274 | FacileMenuItem* fgColor(bool exp, QColor color); 275 | 276 | FacileMenuItem* bgColor(QColor color); 277 | FacileMenuItem* bgColor(bool exp, QColor color); 278 | 279 | // 插入前缀 280 | FacileMenuItem* prefix(bool exp, QString pfix); 281 | FacileMenuItem* prefix(QString pfix); 282 | 283 | // 插入后缀,参数3支持类似 "action后缀 (K)" 这样的格式 284 | FacileMenuItem* suffix(bool exp, QString sfix, bool inLeftParenthesis = true); 285 | FacileMenuItem* suffix(QString sfix, bool inLeftParenthesis = true); 286 | 287 | FacileMenuItem* icon(bool ic, QIcon icon); 288 | 289 | // 设置边界:半径、颜色 290 | FacileMenuItem* borderR(int radius = 3, QColor co = Qt::transparent); 291 | 292 | // 点击后是否保持菜单显示(默认点一下就隐藏菜单) 293 | FacileMenuItem* linger(); 294 | // 点击后保持显示(同linger()),并且修改菜单项文本 295 | FacileMenuItem* lingerText(QString textAfterClick); 296 | 297 | // 点击后的菜单文本改变 298 | textAfterClick(QString newText); 299 | // 根据当前文本修改为新文本的 Lambda 表达式 300 | // 参数示例:[=](QString s) -> QString { if (s == "xx") return "xx"; } 301 | textAfterClick(FuncStringStringType func); 302 | 303 | // 满足 exp 时执行 trueLambda 表达式,否则执行 falseLambda 表达式 304 | FacileMenuItem* ifer(bool exp, trueLambda, falseLambda = nullptr); 305 | 306 | // 逻辑控制 307 | FacileMenuItem* ifer(bool exp); // 满足条件时才继续,下同 308 | FacileMenuItem* elifer(bool exp); 309 | FacileMenuItem* elser(); 310 | 311 | FacileMenuItem* switcher(int value); 312 | FacileMenuItem* caser(int value, matchedLambda); // 匹配时执行Lambda,无需break 313 | FacileMenuItem* caser(int value); // 结束记得breaker(允许忘掉~) 314 | FacileMenuItem* breaker(); 315 | FacileMenuItem* defaulter(); 316 | 317 | // 取消后面所有命令(无视层级,相当于函数中return) 318 | FacileMenuItem* exiter(bool ex = true); 319 | ``` 320 | 321 | 322 | > 注意:由于加了一些容错处理(例如caser可以不用写breaker),无法进行if/switch的多层嵌套(较多的逻辑运算不建议放在菜单中) 323 | 324 | 325 | 326 | ### 动态菜单 327 | 328 | 在 FacileMenu 中先创建一个 item 作为菜单(示例中的 `m`),但是先不创建子菜单,直到鼠标悬浮上去的时候才创建并显示出来。 329 | 330 | 作用是可以显示动态加载的列表,比如**多层级嵌套的目录树**,如果一次性全部加载完那么会很费性能,可以等每次需要加载的时候才读取列表。 331 | 332 | ```C++ 333 | auto m = menu->addMenu("icon", "name", [=]{ /*...*/ }); 334 | menu->lastAddedItem()->setDynamicCreate(true)->setData("data1"); 335 | connect(m, &FacileMenu::signalDynamicMenuTriggered, this, [=](FacileMenuItem* item) { 336 | QString data = item->getData().toString(); // 字符串"data1" 337 | /*...处理代码...*/ 338 | }); 339 | ``` 340 | 341 | #### 示例:目录树 342 | 343 | ```C++ 344 | void showFacileDir(QString path, FacileMenu *parentMenu, int level) 345 | { 346 | // 这是方案一:显示的时候一次性加载所有的目录,但是要限制最大层级,否则文件太多导致卡顿 347 | // if (level >= maxLevel) 348 | // return ; 349 | 350 | // 这是方案二:一个menu相当于一个外层文件夹,需要的时候连接信号,再动态加载目录 351 | auto connectDynamicMenu = [=](FacileMenu* menu) { 352 | connect(menu, &FacileMenu::signalDynamicMenuTriggered, this, [=](FacileMenuItem* item) { 353 | QString path = item->getData().toString(); 354 | if (path.isEmpty()) 355 | { 356 | qWarning() << "无法获取到目录路径" << path; 357 | return; 358 | } 359 | qInfo() << "动态加载目录列表:" << path; 360 | showFacileDir(path, item->subMenu(), -1); 361 | }); 362 | }; 363 | 364 | FacileMenu* menu = parentMenu; 365 | if (!parentMenu) 366 | { 367 | menu = new FacileMenu(this); 368 | connectDynamicMenu(menu); 369 | } 370 | 371 | auto infos = QDir(path).entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); 372 | QFileIconProvider provicer; 373 | int count = 0; // 单个目录的文件数量上限,太多的话就不是菜单的事儿了 374 | foreach (auto info, infos) 375 | { 376 | if (++count > us->fastOpenDirFileCount) 377 | { 378 | menu->addTitle("总计文件(夹)数量:" + QString::number(infos.count())); 379 | break; 380 | } 381 | 382 | if (info.isDir()) 383 | { 384 | auto m = menu->addMenu(provicer.icon(info), info.fileName(), [=]{ 385 | menu->toClose(); // 先关闭菜单,得以隐藏面板;否则即使隐藏也会重新触发enter事件 386 | QDesktopServices::openUrl("file:///" + info.absoluteFilePath()); 387 | if (hideAfterTrigger) 388 | { 389 | QTimer::singleShot(0, [=]{ 390 | emit hidePanel(); 391 | }); 392 | } 393 | }); 394 | // 方案一:一口气加载 395 | // showFacileDir(info.absoluteFilePath(), m, level+1); 396 | 397 | // 方案二:动态加载 398 | menu->lastAddedItem()->setDynamicCreate(true)->setData(QString(info.absoluteFilePath())); 399 | connectDynamicMenu(m); 400 | } 401 | else 402 | { 403 | menu->addAction(provicer.icon(info), info.fileName(), [=]{ 404 | menu->toClose(); 405 | QDesktopServices::openUrl("file:///" + info.absoluteFilePath()); 406 | if (hideAfterTrigger) 407 | { 408 | QTimer::singleShot(0, [=]{ 409 | emit hidePanel(); 410 | }); 411 | } 412 | }); 413 | } 414 | } 415 | 416 | if (!parentMenu) // 表示是最外层的菜单 417 | { 418 | if (level != -1) 419 | menu->exec(); 420 | } 421 | } 422 | ``` 423 | 424 | 425 | 426 | ## 配置项 427 | 428 | ### 菜单颜色与主题 429 | 430 | 整个应用程序统一的菜单设置,即 FacileMenu 类中的静态变量。可自定义,与所用程序的主题绑定。 431 | 432 | 例如: 433 | 434 | ```C++ 435 | FacileMenu::normal_bg = QColor(128, 128, 128); 436 | ``` 437 | 438 | 变量说明: 439 | 440 | | 变量 | 默认值 | 说明 | 441 | | ------------------- | ------- | ------------------------------------------------------------ | 442 | | normal_bg | 白色 | 菜单背景 | 443 | | hover_bg | 灰色 | 鼠标悬浮时的 item 背景 | 444 | | press_bg | 深灰色 | 鼠标按下时的 item 背景 | 445 | | text_fg | 黑色 | 菜单项字体颜色 | 446 | | blur_bg_alpha | 33 | 毛玻璃不透明度,0为不显示毛玻璃 | 447 | | easing_curve | OutBack | 出现的动画曲线 | 448 | | auto_dark_mode | true | 自动判断夜间模式,随背景图变化 | 449 | | auto_theme_by_bg | false | 自动判断多种主题色,会覆盖夜间模式 | 450 | | all_menu_same_color | true | 子菜单与主菜单统一颜色,否则同时出现的多个菜单可以使用不同颜色 | 451 | 452 | 453 | 454 | ### 单个菜单设置 455 | 456 | 一些可选的设置项,按需加上去,也可以都不加。 457 | 458 | ```C++ 459 | FacileMenu* menu = (new FacileMenu(this)) 460 | ->setTipArea("Ctrl+Alt+P") // 设置右边的快捷键提示区域的留空宽度,建议使用最长快捷键 461 | ->setSplitInRow(true) // 横向按钮是否使用分割线分隔 462 | ->setSubMenuShowOnCursor(false) // 设置子菜单是从鼠标位置出现还是右边出现 463 | ->setAppearAnimation(false) // 菜单出现动画 464 | ->setDisappearAnimation(false); // 菜单消失动画 465 | ``` 466 | 467 | 如果多级菜单中会自动将这些设置项**传递给子菜单**。 468 | 469 | 其中仅 `setTipArea` 不会传递给下一级。 470 | 471 | 可以直接修改源码中这些变量的默认值,全部菜单生效。 472 | 473 | 474 | 475 | ## 截图 476 | 477 | ![菜单](screenshots/picture.gif) 478 | 479 | 480 | 481 | ## 菜单栏 482 | 483 | ![菜单栏](screenshots/menubar.gif) 484 | 485 | 结合 FacileMenu 自定义的一个菜单栏,目前不是很完善(做着玩的,但是对这个动画效果确实挺失望的)。 486 | 487 | 具体可参考 MainWindow 中的写法: 488 | 489 | ```C++ 490 | ui->menuBar->setAnimationEnabled(false); // 开启动画 491 | ui->menuBar->addMenu("文件", fileMenu); 492 | ui->menuBar->addMenu("编辑", editMenu); 493 | ui->menuBar->addMenu("查看", viewMenu); 494 | ui->menuBar->addMenu("帮助", helpMenu); 495 | ui->menuBar->insertMenu(2, "格式", formatMenu); 496 | ``` 497 | 498 | 499 | 500 | ## 注意点 501 | 502 | ### 打开**模态对话框**可能会引起崩溃 503 | 504 | 需要在打开模态对话框之前,关闭当前 menu: 505 | 506 | ```C++ 507 | menu->addAction("选择文件", [=]{ 508 | menu->close(); // 需要这句,否则会导致崩溃 509 | QString path = QFileDialog::getOpenFileName(this, "选择文件", prevPath); 510 | // ... 511 | }); 512 | ``` 513 | 514 | 515 | 516 | ### 菜单关闭导致退出程序 517 | 518 | 在 `main.cpp` 中添加以下代码,使窗口关闭后不会退出整个程序: 519 | 520 | ```C++ 521 | QApplication a(argc, argv); 522 | a.setQuitOnLastWindowClosed(false); 523 | ``` 524 | -------------------------------------------------------------------------------- /facile_menu/facilemenuitem.cpp: -------------------------------------------------------------------------------- 1 | #include "facilemenuitem.h" 2 | 3 | FacileMenuItem::FacileMenuItem(QWidget *parent) : InteractiveButtonBase(parent) 4 | { 5 | 6 | } 7 | 8 | FacileMenuItem::FacileMenuItem(QString t, QWidget *parent) : InteractiveButtonBase(t, parent) 9 | { 10 | 11 | } 12 | 13 | FacileMenuItem::FacileMenuItem(QIcon i, QWidget *parent) : InteractiveButtonBase(i, parent) 14 | { 15 | 16 | } 17 | 18 | FacileMenuItem::FacileMenuItem(QIcon i, QString t, QWidget *parent) : InteractiveButtonBase(i, t, parent) 19 | { 20 | 21 | } 22 | 23 | FacileMenuItem::FacileMenuItem(QPixmap p, QString t, QWidget *parent) : InteractiveButtonBase(p, t, parent) 24 | { 25 | 26 | } 27 | 28 | FacileMenuItem *FacileMenuItem::setEnabled(bool e) 29 | { 30 | InteractiveButtonBase::setEnabled(e); 31 | return this; 32 | } 33 | 34 | FacileMenuItem *FacileMenuItem::setCheckable(bool c) 35 | { 36 | checkable = c; 37 | if (c && model != IconText && InteractiveButtonBase::icon.isNull()) 38 | model = IconText; 39 | update(); 40 | return this; 41 | } 42 | 43 | bool FacileMenuItem::isCheckable() const 44 | { 45 | return checkable; 46 | } 47 | 48 | FacileMenuItem *FacileMenuItem::setChecked(bool c) 49 | { 50 | _state = c; 51 | if (InteractiveButtonBase::icon.isNull()) 52 | model = IconText; // 强制显示check空白部分 53 | setCheckable(true); 54 | return this; 55 | } 56 | 57 | bool FacileMenuItem::isChecked() 58 | { 59 | return getState(); 60 | } 61 | 62 | FacileMenuItem *FacileMenuItem::setKey(Qt::Key key) 63 | { 64 | this->key = key; 65 | return this; 66 | } 67 | 68 | bool FacileMenuItem::isKey(Qt::Key key) const 69 | { 70 | return key == this->key; 71 | } 72 | 73 | FacileMenuItem *FacileMenuItem::setSubMenu(FacileMenu *menu) 74 | { 75 | sub_menu = menu; 76 | return this; 77 | } 78 | 79 | bool FacileMenuItem::isSubMenu() const 80 | { 81 | return sub_menu != nullptr; 82 | } 83 | 84 | bool FacileMenuItem::isLinger() const 85 | { 86 | return trigger_linger; 87 | } 88 | 89 | FacileMenuItem *FacileMenuItem::setData(QVariant data) 90 | { 91 | this->data = data; 92 | return this; 93 | } 94 | 95 | QVariant FacileMenuItem::getData() 96 | { 97 | return data; 98 | } 99 | 100 | FacileMenuItem* FacileMenuItem::setDynamicCreate(bool dynamic) 101 | { 102 | if (dynamic) 103 | setDynamicCreateState(-1); 104 | else 105 | setDynamicCreateState(0); 106 | return this; 107 | } 108 | 109 | void FacileMenuItem::setDynamicCreateState(short state) 110 | { 111 | this->dynamic_create_state = state; 112 | } 113 | 114 | short FacileMenuItem::getDynamicCreateState() const 115 | { 116 | return dynamic_create_state; 117 | } 118 | 119 | FacileMenuItem *FacileMenuItem::tip(QString sc) 120 | { 121 | shortcut_tip = sc; 122 | return this; 123 | } 124 | 125 | FacileMenuItem *FacileMenuItem::tip(bool exp, QString sc) 126 | { 127 | if (exp) 128 | tip(sc); 129 | return this; 130 | } 131 | 132 | FacileMenuItem *FacileMenuItem::tooltip(QString tt) 133 | { 134 | setToolTip(tt); 135 | return this; 136 | } 137 | 138 | FacileMenuItem *FacileMenuItem::tooltip(bool exp, QString tt) 139 | { 140 | if (exp) 141 | tooltip(tt); 142 | return this; 143 | } 144 | 145 | FacileMenuItem *FacileMenuItem::triggered(FuncType func) 146 | { 147 | connect(this, &InteractiveButtonBase::clicked, this, [=]{ 148 | func(); 149 | }); 150 | return this; 151 | } 152 | 153 | FacileMenuItem *FacileMenuItem::triggered(bool exp, FuncType func) 154 | { 155 | if (!exp) 156 | triggered(func); 157 | return this; 158 | } 159 | 160 | FacileMenuItem *FacileMenuItem::disable(bool exp) 161 | { 162 | if (exp) 163 | setDisabled(true); 164 | return this; 165 | } 166 | 167 | FacileMenuItem *FacileMenuItem::enable(bool exp) 168 | { 169 | if (exp) 170 | setEnabled(true); 171 | return this; 172 | } 173 | 174 | FacileMenuItem *FacileMenuItem::hide(bool exp) 175 | { 176 | if (exp) 177 | InteractiveButtonBase::hide(); 178 | return this; 179 | } 180 | 181 | /** 182 | * 默认就是show状态 183 | * 为了和show区分开 184 | */ 185 | FacileMenuItem *FacileMenuItem::visible(bool exp) 186 | { 187 | if (exp) 188 | InteractiveButtonBase::setVisible(true); 189 | return this; 190 | } 191 | 192 | FacileMenuItem *FacileMenuItem::check(bool exp) 193 | { 194 | setCheckable(true); 195 | if (exp) 196 | setChecked(true); 197 | else if (InteractiveButtonBase::icon.isNull()) 198 | model = IconText; // 强制显示check空白部分 199 | return this; 200 | } 201 | 202 | FacileMenuItem *FacileMenuItem::uncheck(bool exp) 203 | { 204 | setCheckable(true); 205 | if (exp) 206 | setChecked(false); 207 | return this; 208 | } 209 | 210 | /** 211 | * 切换状态 212 | * 如果选中了,则取消选中;反之亦然 213 | * (本来打算不只是选中状态,然而还没想到其他有什么能切换的) 214 | */ 215 | FacileMenuItem *FacileMenuItem::toggle(bool exp) 216 | { 217 | if (!exp) 218 | return this; 219 | if (isCheckable()) 220 | { 221 | setChecked(!isChecked()); 222 | } 223 | // 以后什么功能想到再加 224 | return this; 225 | } 226 | 227 | /** 228 | * 点击自动切换状态 229 | * 小心点,因为信号槽顺序的关系,若放在triggered后面,可能会出现相反的check 230 | * 建议只用于和顺序无关的自动切换 231 | * (还不知道怎么修改信号槽的调用顺序) 232 | */ 233 | FacileMenuItem *FacileMenuItem::autoToggle() 234 | { 235 | connect(this, &InteractiveButtonBase::clicked, this, [=]{ 236 | toggle(); 237 | }); 238 | return this; 239 | } 240 | 241 | FacileMenuItem *FacileMenuItem::text(bool exp, QString str) 242 | { 243 | if (exp) 244 | { 245 | // 去掉快捷键符号 246 | // 注意:这里设置文字不会改变原来的快捷键! 247 | setText(str.replace(QRegExp("&([\\w\\d])\\b"), "\\1")); 248 | // 调整大小 249 | setFixedForeSize(); 250 | } 251 | return this; 252 | } 253 | 254 | /** 255 | * 设置字符串,成立时 tru,不成立时 fal 256 | * 注意:这里是直接设置完整的文字,不会去掉快捷键&符号 257 | */ 258 | FacileMenuItem *FacileMenuItem::text(bool exp, QString tru, QString fal) 259 | { 260 | if (exp) 261 | setText(tru); 262 | else 263 | setText(fal); 264 | return this; 265 | } 266 | 267 | FacileMenuItem *FacileMenuItem::fgColor(QColor color) 268 | { 269 | setTextColor(color); 270 | return this; 271 | } 272 | 273 | FacileMenuItem *FacileMenuItem::fgColor(bool exp, QColor color) 274 | { 275 | if (exp) 276 | return fgColor(color); 277 | return this; 278 | } 279 | 280 | FacileMenuItem *FacileMenuItem::bgColor(QColor color) 281 | { 282 | setBgColor(color); 283 | return this; 284 | } 285 | 286 | FacileMenuItem *FacileMenuItem::bgColor(bool exp, QColor color) 287 | { 288 | if (exp) 289 | bgColor(color); 290 | return this; 291 | } 292 | 293 | /** 294 | * 满足条件时,text添加前缀 295 | */ 296 | FacileMenuItem *FacileMenuItem::prefix(bool exp, QString pfix) 297 | { 298 | if (exp) 299 | prefix(pfix); 300 | return this; 301 | } 302 | 303 | /** 304 | * 满足条件时,text添加后缀 305 | * @param inLeftParenthesis 支持 text(xxx) 形式,会在左括号前添加后缀 306 | */ 307 | FacileMenuItem *FacileMenuItem::suffix(bool exp, QString sfix, bool inLeftParenthesis) 308 | { 309 | if (exp) 310 | { 311 | suffix(sfix, inLeftParenthesis); 312 | } 313 | return this; 314 | } 315 | 316 | FacileMenuItem *FacileMenuItem::prefix(QString pfix) 317 | { 318 | setText(pfix + getText()); 319 | return this; 320 | } 321 | 322 | FacileMenuItem *FacileMenuItem::suffix(QString sfix, bool inLeftParenthesis) 323 | { 324 | if (!inLeftParenthesis) 325 | { 326 | setText(getText() + sfix); 327 | } 328 | else 329 | { 330 | QString text = getText(); 331 | int index = -1; 332 | if ((index = text.lastIndexOf("(")) > -1) 333 | { 334 | while (index > 0 && text.mid(index-1, 1) == " ") 335 | index--; 336 | } 337 | if (index <= 0) // 没有左括号或者以空格开头,直接加到最后面 338 | { 339 | setText(getText() + sfix); 340 | } 341 | else 342 | { 343 | setText(text.left(index) + sfix + text.right(text.length()-index)); 344 | } 345 | } 346 | return this; 347 | } 348 | 349 | FacileMenuItem *FacileMenuItem::icon(bool exp, QIcon ico) 350 | { 351 | if (exp) 352 | setIcon(ico); 353 | return this; 354 | } 355 | 356 | FacileMenuItem *FacileMenuItem::borderR(int radius, QColor co) 357 | { 358 | setRadius(radius); 359 | if (co != Qt::transparent) 360 | setBorderColor(co); 361 | else 362 | setBorderColor(press_bg); 363 | return this; 364 | } 365 | 366 | FacileMenuItem *FacileMenuItem::linger() 367 | { 368 | trigger_linger = true; 369 | return this; 370 | } 371 | 372 | FacileMenuItem *FacileMenuItem::lingerText(QString textAfterClick) 373 | { 374 | this->linger(); 375 | connect(this, &FacileMenuItem::clicked, this, [=]{ 376 | setText(textAfterClick); 377 | }); 378 | return this; 379 | } 380 | 381 | /** 382 | * 绑定某一布尔类型的变量(只能全局变量) 383 | * 点击即切换值 384 | * 注意:因为是异步的,局部变量会导致崩溃! 385 | */ 386 | FacileMenuItem *FacileMenuItem::bind(bool &val) 387 | { 388 | connect(this, &InteractiveButtonBase::clicked, this, [&]{ 389 | val = !val; 390 | }); 391 | return this; 392 | } 393 | 394 | /** 395 | * 短期长按效果 396 | * 该操作不会影响其它任何交互效果 397 | * 即不会隐藏菜单,也不会解除单击信号 398 | */ 399 | FacileMenuItem *FacileMenuItem::longPress(FuncType func) 400 | { 401 | connect(this, &InteractiveButtonBase::signalMousePressLater, this, [=](QMouseEvent*){ 402 | func(); 403 | }); 404 | return this; 405 | } 406 | 407 | /** 408 | * 点击菜单后的新文本 409 | * 一般用于点击后不隐藏 或者 重新显示的菜单 410 | * 不然没必要设置,点击后就没了 411 | */ 412 | FacileMenuItem *FacileMenuItem::textAfterClick(QString newText) 413 | { 414 | connect(this, &FacileMenuItem::clicked, this, [=]{ 415 | setText(newText); 416 | }); 417 | return this; 418 | } 419 | 420 | FacileMenuItem *FacileMenuItem::textAfterClick(FuncStringStringType func) 421 | { 422 | connect(this, &FacileMenuItem::clicked, this, [=]{ 423 | setText(func(this->InteractiveButtonBase::text)); 424 | }); 425 | return this; 426 | } 427 | 428 | /** 429 | * 适用于连续设置 430 | * 当 iff 成立时继续 431 | * 否则取消后面所有设置 432 | */ 433 | FacileMenuItem *FacileMenuItem::ifer(bool exp) 434 | { 435 | if (exp) 436 | return this; 437 | 438 | // 返回一个无用item,在自己delete时也delete掉 439 | return createTempItem(); 440 | } 441 | 442 | /** 443 | * 完全等于 ifer 444 | * 如果已经在 ifer 里面,则先退出 445 | */ 446 | FacileMenuItem *FacileMenuItem::elifer(bool exp) 447 | { 448 | if (parent_menu_item_in_if) // ifer 不成立后的,退出并转至新的 ifer 449 | return parent_menu_item_in_if->ifer(exp); 450 | return ifer(exp); // 直接使用,完全等同于 ifer 451 | } 452 | 453 | FacileMenuItem *FacileMenuItem::elser() 454 | { 455 | if (parent_menu_item_in_if) 456 | return parent_menu_item_in_if; 457 | return createTempItem(); 458 | } 459 | 460 | /** 461 | * 适用于连续设置action时,满足条件则退出 462 | * 相当于一个控制语句 463 | * 当ex成立时,取消后面所有设置 464 | */ 465 | FacileMenuItem *FacileMenuItem::exiter(bool exp) 466 | { 467 | if (!exp) 468 | return this; 469 | 470 | // 返回一个无用item,在自己delete时也delete掉 471 | return createTempItem(false); 472 | } 473 | 474 | /** 475 | * 适用于连续设置 476 | * 满足某一条件则执行 func(this) 477 | */ 478 | FacileMenuItem *FacileMenuItem::ifer(bool exp, FuncItemType func, FuncItemType elseFunc) 479 | { 480 | if (exp) 481 | { 482 | if (func) 483 | func(this); 484 | } 485 | else 486 | { 487 | if (elseFunc) 488 | elseFunc(this); 489 | } 490 | return this; 491 | } 492 | 493 | /** 494 | * 适用于连续设置 495 | * 类似 switch 语句,输入判断的值 496 | * 当后续的 caser 满足 value 时,允许执行 caser 的 func 或后面紧跟着的的设置 497 | */ 498 | FacileMenuItem *FacileMenuItem::switcher(int value) 499 | { 500 | switch_value = value; 501 | switch_matched = false; 502 | return this; 503 | } 504 | 505 | /** 506 | * 当 value 等同于 switcher 判断的 value 时,执行 func 507 | * 并返回原始 item 508 | * 注意与重载的 caser(int) 进行区分 509 | */ 510 | FacileMenuItem *FacileMenuItem::caser(int value, FuncType func) 511 | { 512 | if (value == switch_value) 513 | { 514 | switch_matched = true; 515 | if (func) 516 | func(); 517 | } 518 | return this; 519 | } 520 | 521 | /** 522 | * 当 value 等同于 switcher 的 value 时,返回原始 item 523 | * 即执行 caser 后面的设置,直至 breaker 524 | * 注意与重载的 caser(int FuncType) 进行区分 525 | */ 526 | FacileMenuItem *FacileMenuItem::caser(int value) 527 | { 528 | // 可能已经接着一个没有 breaker 的 caser 529 | // 则回到上一级(这样会导致无法嵌套) 530 | if (this->parent_menu_item_in_if) 531 | { 532 | // 接着一个 !=的caser 后面 533 | if (value == parent_menu_item_in_if->switch_value) 534 | { 535 | parent_menu_item_in_if->switch_matched = true; 536 | return parent_menu_item_in_if; // 真正需要使用的实例 537 | } 538 | return this; // 继续使用自己(一个临时实例) 539 | } 540 | else // 自己是第一个 caser 或者 ==的caser 后面 541 | { 542 | if (value == switch_value) 543 | { 544 | switch_matched = true; 545 | return this; 546 | } 547 | return createTempItem(); 548 | } 549 | } 550 | 551 | /** 552 | * caser 的 value 不等于 switcher 的 value 时 553 | * 此语句用来退出 554 | */ 555 | FacileMenuItem *FacileMenuItem::breaker() 556 | { 557 | if (parent_menu_item_in_if) 558 | return parent_menu_item_in_if; 559 | return this; // 应该不会吧…… 560 | } 561 | 562 | /** 563 | * 如果switcher的caser没有满足 564 | */ 565 | FacileMenuItem *FacileMenuItem::defaulter() 566 | { 567 | if (switch_matched) // 已经有 caser 匹配了 568 | return createTempItem(); // 返回无效临时实例 569 | return this; // 能用,返回自己 570 | } 571 | 572 | /** 573 | * 返回自己的子菜单对象 574 | */ 575 | FacileMenu *FacileMenuItem::subMenu() 576 | { 577 | return sub_menu; 578 | } 579 | 580 | void FacileMenuItem::paintEvent(QPaintEvent *event) 581 | { 582 | InteractiveButtonBase::paintEvent(event); 583 | 584 | int right = width()- 8; 585 | 586 | QPainter painter(this); 587 | if (isSubMenu()) 588 | { 589 | right -= icon_text_size; 590 | // 画右边箭头的图标 591 | QRect rect(right, fore_paddings.top, icon_text_size, icon_text_size); 592 | painter.drawPixmap(rect, QPixmap(":/icons/sub_menu_arrow")); 593 | } 594 | 595 | right -= icon_text_padding; 596 | if (!shortcut_tip.isEmpty()) 597 | { 598 | // 画右边的文字 599 | QFontMetrics fm(this->font()); 600 | int width = fm.horizontalAdvance(shortcut_tip); 601 | painter.save(); 602 | auto c = painter.pen().color(); 603 | c.setAlpha(c.alpha() / 2); 604 | painter.setPen(c); 605 | painter.drawText(QRect(right-width, fore_paddings.top, width, height()-fore_paddings.top-fore_paddings.bottom), 606 | Qt::AlignRight, shortcut_tip); 607 | painter.restore(); 608 | } 609 | } 610 | 611 | void FacileMenuItem::drawIconBeforeText(QPainter &painter, QRect icon_rect) 612 | { 613 | // 选中 614 | if (checkable) 615 | { 616 | QPainterPath path; 617 | QRect expand_rect = icon_rect; 618 | expand_rect.adjust(-2, -2, 2, 2); 619 | path.addRoundedRect(expand_rect, 3, 3); 620 | if (InteractiveButtonBase::icon.isNull()) 621 | { 622 | // 绘制√ 623 | if (isChecked()) 624 | painter.drawText(icon_rect, "√"); 625 | } 626 | else // 有图标,使用 627 | { 628 | if (getState()) 629 | { 630 | // 绘制选中样式: 圆角矩形 631 | painter.fillPath(path, press_bg); 632 | } 633 | else 634 | { 635 | // 绘制未选中样式:空白边框 636 | painter.save(); 637 | painter.setPen(QPen(press_bg, 1)); 638 | painter.drawPath(path); 639 | painter.restore(); 640 | } 641 | } 642 | } 643 | 644 | InteractiveButtonBase::drawIconBeforeText(painter, icon_rect); 645 | } 646 | 647 | FacileMenuItem *FacileMenuItem::createTempItem(bool thisIsParent) 648 | { 649 | auto useless = new FacileMenuItem(QIcon(), "", this); 650 | useless->parent_menu_item_in_if = thisIsParent ? this : nullptr; 651 | useless->hide(); 652 | useless->setEnabled(false); 653 | useless->setMinimumSize(0, 0); 654 | useless->setFixedSize(0, 0); 655 | useless->move(-999, -999); 656 | return useless; 657 | } 658 | -------------------------------------------------------------------------------- /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 | #include 18 | 19 | #define PI 3.1415926 20 | #define GOLDEN_RATIO 0.618 21 | 22 | #define DOUBLE_PRESS_INTERVAL 500 // /* 300 */松开和按下的间隔。相等为双击 23 | #define SINGLE_PRESS_INTERVAL 200 // /* 150 */按下时间超过这个数就是单击。相等为单击 24 | 25 | /** 26 | * Copyright (c) 2019 命燃芯乂 All rights reserved. 27 | × 28 | * 邮箱:wxy@iwxyi.com 29 | * QQ号:482582886 30 | * 时间:2020.12.28 31 | * 32 | * 说明:灵性的自定义按钮,简单又有趣 33 | * 源码:https://github.com/MRXY001/Interactive-Windows-Buttons 34 | * 35 | * 本代码为本人编写方便自己使用,现在无私送给大家免费使用。 36 | * 程序版权归作者所有,只可使用不能出售,违反者本人有权追究责任。 37 | */ 38 | 39 | class InteractiveButtonBase : public QPushButton 40 | { 41 | Q_OBJECT 42 | Q_PROPERTY(bool self_enabled READ getSelfEnabled WRITE setSelfEnabled) // 是否启用自定义的按钮(true) 43 | Q_PROPERTY(bool parent_enabled READ getParentEnabled WRITE setParentEnabled) // 是否启用父类按钮(false) 44 | Q_PROPERTY(bool fore_enabled READ getForeEnabled WRITE setForeEnabled) // 是否绘制自定义按钮前景色(true) 45 | Q_PROPERTY(QString text READ getText WRITE setText) // 前景文字 46 | Q_PROPERTY(QString icon_path READ getIconPath WRITE setIconPath) // 前景图标 47 | Q_PROPERTY(QString pixmap_path READ getPixmapPath WRITE setPixmapPath) // 前景图标 48 | Q_PROPERTY(QColor icon_color READ getIconColor WRITE setIconColor) // 前景图标帅色 49 | Q_PROPERTY(QColor text_color READ getTextColor WRITE setTextColor) // 前景文字颜色 50 | Q_PROPERTY(QColor background_color READ getNormalColor WRITE setNormalColor) // 背景颜色 51 | Q_PROPERTY(QColor border_color READ getBorderColor WRITE setBorderColor) // 边界颜色 52 | Q_PROPERTY(QColor hover_color READ getHoverColor WRITE setHoverColor) // 鼠标悬浮背景颜色 53 | Q_PROPERTY(QColor press_color READ getPressColor WRITE setPressColor) // 鼠标按下背景颜色 54 | Q_PROPERTY(int hover_duration READ getHoverAniDuration WRITE setHoverAniDuration) // 鼠标悬浮动画周期 55 | Q_PROPERTY(int press_duration READ getPressAniDuration WRITE setPressAniDuration) // 鼠标按下动画周期 56 | Q_PROPERTY(int click_duration READ getClickAniDuration WRITE setClickAniDuration) // 鼠标点击动画周期 57 | Q_PROPERTY(double icon_padding_proper READ getIconPaddingProper WRITE setIconPaddingProper) // 图标四边空白处大小比例 58 | Q_PROPERTY(int radius READ getRadius WRITE setRadius) // 边框圆角半径 59 | Q_PROPERTY(int border_width READ getBorderWidth WRITE setBorderWidth) // 边框线条粗细 60 | Q_PROPERTY(bool fixed_fore_pos READ getFixedTextPos WRITE setFixedTextPos) // 是否固定前景位置(false) 61 | Q_PROPERTY(bool text_dynamic_size READ getTextDynamicSize WRITE setTextDynamicSize) // 修改字体大小时调整按钮最小尺寸(false) 62 | Q_PROPERTY(bool leave_after_clicked READ getLeaveAfterClick WRITE setLeaveAfterClick) // 鼠标单击松开后取消悬浮效果(针对菜单、弹窗) 63 | Q_PROPERTY(bool show_animation READ getShowAni WRITE setShowAni) // 是否启用出现动画(鼠标移开则消失)(false) 64 | Q_PROPERTY(bool water_animation READ getWaterRipple WRITE setWaterRipple) // 是否启用点击水波纹动画(否则使用渐变)(true) 65 | Q_PROPERTY(int font_size READ getFontSizeT WRITE setFontSizeT) // 动:按钮字体动画效果(自动,不应该设置) 66 | public: 67 | InteractiveButtonBase(QWidget *parent = nullptr); 68 | InteractiveButtonBase(QString text, QWidget *parent = nullptr); 69 | InteractiveButtonBase(QIcon icon, QWidget *parent = nullptr); 70 | InteractiveButtonBase(QPixmap pixmap, QWidget *parent = nullptr); 71 | InteractiveButtonBase(QIcon icon, QString text, QWidget *parent = nullptr); 72 | InteractiveButtonBase(QPixmap pixmap, QString text, QWidget *parent = nullptr); 73 | 74 | /** 75 | * 前景实体 76 | */ 77 | enum PaintModel 78 | { 79 | None, // 无前景,仅使用背景 80 | Text, // 纯文字(替代父类) 81 | Icon, // 纯图标 82 | PixmapMask, // 可变色图标(通过pixmap+遮罩实现),锯齿化明显 83 | IconText, // 图标+文字(强制左对齐) 84 | PixmapText // 变色图标+文字(强制左对齐) 85 | }; 86 | 87 | /** 88 | * 四周边界的padding 89 | * 调整按钮大小时:宽度+左右、高度+上下 90 | */ 91 | struct EdgeVal 92 | { 93 | EdgeVal() {} 94 | EdgeVal(int l, int t, int r, int b) : left(l), top(t), right(r), bottom(b) {} 95 | int left = 0, top = 0, right = 0, bottom = 0; // 四个边界的空白距离 96 | 97 | bool operator==(const EdgeVal& another) 98 | { 99 | return left == another.left && top == another.top && right == another.right && bottom == another.right; 100 | } 101 | 102 | bool operator!= (const EdgeVal& another) 103 | { 104 | return !((*this) == another); 105 | } 106 | }; 107 | 108 | /** 109 | * 前景额外的图标(可以多个) 110 | * 可能是角标(比如展开箭头) 111 | * 可能是前缀(图例) 112 | */ 113 | struct PaintAddin 114 | { 115 | PaintAddin() : enable(false) {} 116 | PaintAddin(QIcon i, Qt::Alignment a, QSize s) : enable(true), icon(i), align(a), size(s) {} 117 | PaintAddin(QPixmap p, Qt::Alignment a, QSize s) : enable(true), pixmap(p), align(a), size(s) {} 118 | bool enable; // 是否启用 119 | QIcon icon; // 不可变色图标(优先) 120 | QPixmap pixmap; // 可变色图标 121 | Qt::Alignment align; // 对齐方式 122 | QSize size; // 固定大小 123 | EdgeVal padding; // 四边大小 124 | }; 125 | 126 | /** 127 | * 鼠标松开时抖动动画 128 | * 松开的时候计算每一次抖动距离+时间,放入队列中 129 | * 定时调整抖动的队列实体索引 130 | */ 131 | struct Jitter 132 | { 133 | Jitter(QPointF p, qint64 t) : point(p), timestamp(t) {} 134 | QPointF point; // 要运动到的目标坐标 135 | qint64 timestamp; // 运动到目标坐标应该的时间戳,结束后删除本次抖动路径对象 136 | }; 137 | 138 | /** 139 | * 鼠标按下/弹起水波纹动画 140 | * 鼠标按下时动画速度慢(压住),松开后动画速度骤然加快 141 | * 同样用队列记录所有的水波纹动画实体 142 | */ 143 | struct Water 144 | { 145 | Water(QPointF p, qint64 t) : point(p), progress(0), press_timestamp(t), 146 | release_timestamp(0), finish_timestamp(0), finished(false) {} 147 | QPointF point; 148 | int progress; // 水波纹进度100%(已弃用,当前使用时间戳) 149 | qint64 press_timestamp; // 鼠标按下时间戳 150 | qint64 release_timestamp; // 鼠标松开时间戳。与按下时间戳、现行时间戳一起成为水波纹进度计算参数 151 | qint64 finish_timestamp; // 结束时间戳。与当前时间戳相减则为渐变消失经过的时间戳 152 | bool finished; // 是否结束。结束后改为渐变消失 153 | }; 154 | 155 | enum NolinearType 156 | { 157 | Linear, 158 | SlowFaster, 159 | FastSlower, 160 | SlowFastSlower, 161 | SpringBack20, 162 | SpringBack50 163 | }; 164 | 165 | virtual void setText(QString text); 166 | virtual void setIconPath(QString path); 167 | virtual void setIcon(QIcon icon); 168 | virtual void setPixmapPath(QString path); 169 | virtual void setPixmap(QPixmap pixmap); 170 | virtual void setSvgPath(QString path); 171 | virtual void setPaintAddin(QIcon icon, Qt::Alignment align = Qt::AlignRight, QSize size = QSize(32, 32)); 172 | virtual void setPaintAddin(QPixmap pixmap, Qt::Alignment align = Qt::AlignRight, QSize size = QSize(0, 0)); 173 | virtual void setPaintAddinPadding(int horizonal, int vertival); 174 | virtual void setPaintAddinPadding(int left, int top, int right, int bottom); 175 | 176 | void setSelfEnabled(bool e = true); 177 | void setParentEnabled(bool e = false); 178 | void setForeEnabled(bool e = true); 179 | 180 | void setHoverAniDuration(int d); 181 | void setPressAniDuration(int d); 182 | void setClickAniDuration(int d); 183 | void setWaterAniDuration(int press, int release, int finish); 184 | void setWaterRipple(bool enable = true); 185 | void setJitterAni(bool enable = true); 186 | void setUnifyGeomerey(bool enable = true); 187 | void setBgColor(QColor bg); 188 | void setBgColor(QColor hover, QColor press); 189 | void setNormalColor(QColor color); 190 | void setBorderColor(QColor color); 191 | void setHoverColor(QColor color); 192 | void setPressColor(QColor color); 193 | void setIconColor(QColor color = QColor(0, 0, 0)); 194 | void setTextColor(QColor color = QColor(0, 0, 0)); 195 | void setFocusBg(QColor color); 196 | void setFocusBorder(QColor color); 197 | void setFontSize(int f); 198 | void setHover(); 199 | void setAlign(Qt::Alignment a); 200 | void setRadius(int r); 201 | void setRadius(int rx, int ry); 202 | void setBorderWidth(int x); 203 | void setDisabled(bool dis = true); 204 | void setPaddings(int l, int r, int t, int b); 205 | void setPaddings(int h, int v); 206 | void setPaddings(int x); 207 | void setIconPaddingProper(double x); 208 | void setFixedForePos(bool f = true); 209 | void setFixedForeSize(bool f = true, int addin = 0); 210 | void setSquareSize(); 211 | void setTextDynamicSize(bool d = true); 212 | void setLeaveAfterClick(bool l = true); 213 | void setDoubleClicked(bool e = true); 214 | void setAutoTextColor(bool a = true); 215 | void setPretendFocus(bool f = true); 216 | void setBlockHover(bool b = true); 217 | 218 | void setShowAni(bool enable = true); 219 | void showForeground(); 220 | void showForeground2(QPoint point = QPoint(0, 0)); 221 | void hideForeground(); 222 | void delayShowed(int time, QPoint point = QPoint(0, 0)); 223 | 224 | QString getText(); 225 | void setMenu(QMenu *menu); 226 | void adjustMinimumSize(); 227 | void setState(bool s = true); 228 | bool getState(); 229 | virtual void simulateStatePress(bool s = true, bool a = false); 230 | bool isHovering() { return hovering; } 231 | bool isPressing() { return pressing; } 232 | void simulateHover(); 233 | void discardHoverPress(bool force = false); 234 | 235 | bool getSelfEnabled() { return self_enabled; } 236 | bool getParentEnabled() { return parent_enabled; } 237 | bool getForeEnabled() { return fore_enabled; } 238 | QColor getIconColor() { return icon_color; } 239 | QColor getTextColor() { return text_color; } 240 | QColor getNormalColor() { return normal_bg; } 241 | QColor getBorderColor() { return border_bg; } 242 | QColor getHoverColor() { return hover_bg; } 243 | QColor getPressColor() { return press_bg; } 244 | QString getIconPath() { return ""; } 245 | QString getPixmapPath() { return ""; } 246 | int getHoverAniDuration() { return hover_bg_duration; } 247 | int getPressAniDuration() { return press_bg_duration; } 248 | int getClickAniDuration() { return click_ani_duration; } 249 | double getIconPaddingProper() { return icon_padding_proper; } 250 | int getRadius() { return qMax(radius_x, radius_y); } 251 | int getBorderWidth() { return border_width; } 252 | bool getFixedTextPos() { return fixed_fore_pos; } 253 | bool getTextDynamicSize() { return text_dynamic_size; } 254 | bool getLeaveAfterClick() { return leave_after_clicked; } 255 | bool getShowAni() { return show_animation; } 256 | bool getWaterRipple() { return water_animation; } 257 | 258 | #if QT_DEPRECATED_SINCE(5, 11) 259 | QT_DEPRECATED_X("Use InteractiveButtonBase::setFixedForePos(bool fixed = true)") 260 | void setFixedTextPos(bool f = true); 261 | #endif 262 | 263 | protected: 264 | void enterEvent(QEvent *event) override; 265 | void leaveEvent(QEvent *event) override; 266 | void mousePressEvent(QMouseEvent *event) override; 267 | void mouseReleaseEvent(QMouseEvent *event) override; 268 | void mouseMoveEvent(QMouseEvent *event) override; 269 | void resizeEvent(QResizeEvent *event) override; 270 | void focusInEvent(QFocusEvent *event) override; 271 | void focusOutEvent(QFocusEvent *event) override; 272 | void changeEvent(QEvent *event) override; 273 | void paintEvent(QPaintEvent *event) override; 274 | 275 | virtual bool inArea(QPoint point); 276 | virtual bool inArea(QPointF point); 277 | virtual QPainterPath getBgPainterPath(); 278 | virtual QPainterPath getWaterPainterPath(Water water); 279 | virtual void drawIconBeforeText(QPainter &painter, QRect icon_rect); 280 | 281 | QRectF getUnifiedGeometry(); 282 | void updateUnifiedGeometry(); 283 | void paintWaterRipple(QPainter &painter); 284 | void setJitter(); 285 | 286 | int getFontSizeT(); 287 | void setFontSizeT(int f); 288 | 289 | int max(int a, int b) const; 290 | int min(int a, int b) const; 291 | int quick_sqrt(long X) const; 292 | qint64 getTimestamp() const; 293 | bool isLightColor(QColor color); 294 | int getSpringBackProgress(int x, int max); 295 | QColor getOpacityColor(QColor color, double level = 0.5); 296 | QPixmap getMaskPixmap(QPixmap p, QColor c); 297 | 298 | double getNolinearProg(int p, NolinearType type); 299 | QIcon::Mode getIconMode(); 300 | void reloadSvgColor(); 301 | 302 | signals: 303 | void showAniFinished(); 304 | void hideAniFinished(); 305 | void pressAppearAniFinished(); 306 | void pressDisappearAniFinished(); 307 | void jitterAniFinished(); 308 | void doubleClicked(); 309 | void rightClicked(); 310 | void signalFocusIn(); 311 | void signalFocusOut(); 312 | 313 | void signalMouseEnter(); 314 | void signalMouseEnterLater(); // 进入后延迟信号(以渐变动画完成为准,相当于可手动设置) 315 | void signalMouseLeave(); 316 | void signalMouseLeaveLater(); // 离开后延迟的信号(直至渐变动画完成(要是划过一下子离开,这个也会变快)) 317 | void signalMousePress(QMouseEvent *event); 318 | void signalMousePressLater(QMouseEvent *event); 319 | void signalMouseRelease(QMouseEvent *event); 320 | void signalMouseReleaseLater(QMouseEvent *event); 321 | 322 | public slots: 323 | virtual void anchorTimeOut(); 324 | virtual void slotClicked(); 325 | void slotCloseState(); 326 | 327 | protected: 328 | PaintModel model; 329 | QIcon icon; 330 | QString svg_path; 331 | QString text; 332 | QPixmap pixmap; 333 | QSvgRenderer* svg_render = nullptr; 334 | PaintAddin paint_addin; 335 | EdgeVal fore_paddings; 336 | 337 | protected: 338 | // 总体开关 339 | bool self_enabled, parent_enabled, fore_enabled; // 是否启用子类、启动父类、绘制子类前景 340 | 341 | // 出现前景的动画 342 | bool show_animation, show_foreground; 343 | bool show_ani_appearing, show_ani_disappearing; 344 | int show_duration; 345 | qint64 show_timestamp, hide_timestamp; 346 | int show_ani_progress; 347 | QPointF show_ani_point; 348 | QRectF paint_rect; 349 | 350 | // 鼠标开始悬浮、按下、松开、离开的坐标和时间戳 351 | // 鼠标锚点、目标锚点、当前锚点的坐标;当前XY的偏移量 352 | QPointF enter_pos, press_pos, release_pos, mouse_pos, anchor_pos /*目标锚点渐渐靠近鼠标*/; 353 | QPointF offset_pos /*当前偏移量*/, effect_pos, release_offset; // 相对中心、相对左上角、弹起时的平方根偏移 354 | bool hovering, pressing; // 是否悬浮和按下的状态机 355 | qint64 hover_timestamp, leave_timestamp, press_timestamp, release_timestamp; // 各种事件的时间戳 356 | int hover_bg_duration, press_bg_duration, click_ani_duration; // 各种动画时长 357 | 358 | // 定时刷新界面(保证动画持续) 359 | QTimer *anchor_timer; 360 | int move_speed; 361 | 362 | // 背景与前景 363 | QColor icon_color, text_color; // 前景颜色 364 | QColor normal_bg, hover_bg, press_bg, border_bg; // 各种背景颜色 365 | QColor focus_bg, focus_border; // 有焦点的颜色 366 | int hover_speed, press_start, press_speed; // 颜色渐变速度 367 | int hover_progress, press_progress; // 颜色渐变进度 368 | double icon_padding_proper; // 图标的大小比例 369 | int icon_text_padding, icon_text_size; // 图标+文字模式共存时,两者间隔、图标大小 370 | int border_width; 371 | int radius_x, radius_y; 372 | int font_size; 373 | bool fixed_fore_pos; // 鼠标进入时是否固定文字位置 374 | bool fixed_fore_size; // 鼠标进入/点击时是否固定前景大小 375 | bool text_dynamic_size; // 设置字体时自动调整最小宽高 376 | bool auto_text_color; // 动画时是否自动调整文字颜色 377 | bool focusing; // 是否获得了焦点 378 | 379 | // 鼠标单击动画 380 | bool click_ani_appearing, click_ani_disappearing; // 是否正在按下的动画效果中 381 | int click_ani_progress; // 按下的进度(使用时间差计算) 382 | QMouseEvent *mouse_press_event, *mouse_release_event; 383 | 384 | // 统一绘制图标的区域(从整个按钮变为中心三分之二,并且根据偏移计算) 385 | bool unified_geometry; // 上面用不到的话,这个也用不到…… 386 | double _l, _t, _w, _h; 387 | 388 | // 鼠标拖拽弹起来回抖动效果 389 | bool jitter_animation; // 是否开启鼠标松开时的抖动效果 390 | double elastic_coefficient; // 弹性系数 391 | QList jitters; 392 | int jitter_duration; // 抖动一次,多次效果叠加 393 | 394 | // 鼠标按下水波纹动画效果 395 | bool water_animation; // 是否开启水波纹动画 396 | QList waters; 397 | int water_press_duration, water_release_duration, water_finish_duration; 398 | int water_radius; 399 | 400 | // 其他效果 401 | Qt::Alignment align; // 文字/图标对其方向 402 | bool _state; // 一个记录状态的变量,比如是否持续 403 | bool leave_after_clicked; // 鼠标单击松开后取消悬浮效果(针对菜单、弹窗),按钮必定失去焦点 404 | bool _block_hover; // 如果有出现动画,临时屏蔽hovering效果 405 | 406 | // 双击 407 | bool double_clicked; // 开启双击 408 | QTimer *double_timer; // 双击时钟 409 | bool double_prevent; // 双击阻止单击release的flag 410 | }; 411 | 412 | #endif // INTERACTIVEBUTTONBASE_H 413 | -------------------------------------------------------------------------------- /color_octree/imageutil.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "imageutil.h" 3 | 4 | /** 5 | * 图片转换为颜色集 6 | */ 7 | QColor ImageUtil::getImageAverageColor(QImage image, int maxSize) 8 | { 9 | int hCount = image.width(); 10 | int vCount = image.height(); 11 | 12 | if (hCount > maxSize || vCount > maxSize) // 数量过大,按比例缩减 13 | { 14 | double prop = (double)maxSize / qMax(hCount, vCount); 15 | image.scaledToWidth(image.width() * prop); // 缩放到最大大小 16 | } 17 | 18 | long long sumr = 0, sumg = 0, sumb = 0; 19 | 20 | int w = image.width(), h = image.height(); 21 | int sumSize = w * h; 22 | for (int y = 0; y < h; y++) 23 | { 24 | QRgb *line = (QRgb *)image.scanLine(y); 25 | for (int x = 0; x < w; x++) 26 | { 27 | int r = qRed(line[x]), g = qGreen(line[x]), b = qBlue(line[x]); 28 | sumr += r; 29 | sumg += g; 30 | sumb += b; 31 | } 32 | } 33 | 34 | sumr /= sumSize; 35 | sumg /= sumSize; 36 | sumb /= sumSize; 37 | return QColor(sumr, sumg, sumb); 38 | } 39 | 40 | /** 41 | * 获取图片中所有的颜色 42 | */ 43 | QList ImageUtil::extractImageThemeColors(QImage image, int count) 44 | { 45 | auto octree = ColorOctree(image, IMAGE_CALC_PIXEL_MAX_SIZE, count); 46 | auto result = octree.result(); 47 | 48 | if (!result.size() || result.first().count <= 0) 49 | return result; 50 | 51 | // 可以过滤太少的颜色,看情况开关 52 | int maxCount = result.first().count; 53 | int minCount = maxCount / 1000; // 小于最大项1‰的都去掉 54 | while (result.last().count < minCount || result.size() > count) 55 | result.removeLast(); 56 | 57 | return result; 58 | } 59 | 60 | /** 61 | * 获取图片中的所有主题色 62 | * 使用最小差值法一一求差 63 | * 然后和调色盘中颜色对比 64 | * 取出每种最是相近的颜色 65 | */ 66 | QList ImageUtil::extractImageThemeColorsInPalette(QImage image, QList paletteColors, int needCount) 67 | { 68 | auto octree = ColorOctree(image, IMAGE_CALC_PIXEL_MAX_SIZE, paletteColors.size()); 69 | auto result = octree.result(); 70 | 71 | QList colors; 72 | for (int i = 0; i < result.size(); i++) 73 | { 74 | auto cc = result.at(i); 75 | QColor nestColor; 76 | int minVariance = 255*255*3+1; 77 | for (int j = 0; j < paletteColors.size(); j++) 78 | { 79 | auto qc = paletteColors.at(j); 80 | 81 | // 差值计算:直接计算方差吧 82 | int dr = cc.red - qc.red(); 83 | int dg = cc.green - qc.green(); 84 | int db = cc.blue - qc.blue(); 85 | int variance = dr * dr + dg * dg + db * db; 86 | 87 | if (minVariance > variance) 88 | { 89 | minVariance = variance; 90 | nestColor = qc; 91 | } 92 | } 93 | // 找到方差最小的颜色 94 | colors.append(nestColor); 95 | } 96 | 97 | return colors; 98 | } 99 | 100 | /** 101 | * 获取反色 102 | */ 103 | QColor ImageUtil::getInvertColor(QColor color) 104 | { 105 | auto getInvert = [=](int c) -> int{ 106 | if (c < 96 || c > 160) 107 | return 255 - c; 108 | else if (c < 128) 109 | return 255; 110 | else 111 | return 0; 112 | }; 113 | 114 | color.setRed(getInvert(color.red())); 115 | color.setGreen(getInvert(color.green())); 116 | color.setBlue(getInvert(color.blue())); 117 | return color; 118 | } 119 | 120 | /** 121 | * 获取一张图片中应有的背景色、前景色 122 | * 背景色是颜色占比最高的 123 | * 前景色是与其余颜色的(色差*数量)方差和最大的 124 | * @return 颜色提取是否成功 125 | */ 126 | bool ImageUtil::getBgFgColor(QList colors, QColor *bg, QColor *fg) 127 | { 128 | Q_ASSERT(bg && fg); 129 | if (!colors.size()) 130 | { 131 | *bg = Qt::white; 132 | *fg = Qt::black; 133 | return false; 134 | } 135 | 136 | if (colors.size() == 1) 137 | { 138 | *bg = colors.first().toColor(); 139 | *fg = getInvertColor(*bg); 140 | return false; 141 | } 142 | 143 | int maxIndex = -1; 144 | qint64 maxVariance = 0; 145 | int size = colors.size(); 146 | for (int i = 0; i < size; i++) 147 | { 148 | ColorOctree::ColorCount c = colors.at(i); 149 | int r = c.red, g = c.green, b = c.blue, n = c.count; 150 | 151 | qint64 sumVariance = 0; 152 | for (int j = 0; j < size; j++) 153 | { 154 | if (j == i) 155 | continue; 156 | ColorOctree::ColorCount c2 = colors.at(j); 157 | qint64 variant = 3 * (r - c2.red) * (r - c2.red) 158 | + 4 * (g - c2.green) * (g - c2.green) 159 | + 2 * (b - c2.blue) * (b - c2.blue); 160 | variant *= c2.count; 161 | sumVariance += variant; 162 | } 163 | 164 | if (sumVariance > maxVariance) 165 | { 166 | maxVariance = sumVariance; 167 | maxIndex = i; 168 | } 169 | } 170 | 171 | *bg = colors.first().toColor(); 172 | *fg = colors.at(maxIndex).toColor(); 173 | return true; 174 | } 175 | 176 | /** 177 | * 同上,外加一个辅助色 178 | */ 179 | bool ImageUtil::getBgFgSgColor(QList colors, QColor *bg, QColor *fg, QColor *sg) 180 | { 181 | // 调用上面先获取背景色、前景色 182 | if (!getBgFgColor(colors, bg, fg)) 183 | return false; 184 | 185 | // 再获取合适的辅助色 186 | if (colors.size() == 2) 187 | { 188 | *sg = getInvertColor(*fg); // 文字取反 189 | return false; 190 | } 191 | 192 | int maxIndex = -1; 193 | qint64 maxVariance = 0; 194 | for (int i = 0; i < colors.size(); i++) 195 | { 196 | ColorOctree::ColorCount c = colors.at(i); 197 | if (c.toColor() == *bg || c.toColor() == *fg) 198 | continue; 199 | 200 | int r = c.red, g = c.green, b = c.blue, n = c.count; 201 | qint64 variantBg = 3 * (r - bg->red()) * (r - bg->red()) 202 | + 4 * (g - bg->green()) * (g - bg->green()) 203 | + 2 * (b - bg->blue()) * (b - bg->blue()); 204 | qint64 variantFg = 3 * (r - fg->red()) * (r - fg->red()) 205 | + 4 * (g - fg->green()) * (g - fg->green()) 206 | + 2 * (b - fg->blue()) * (b - fg->blue()); 207 | qint64 sum = variantBg + variantFg * 2; // 文字占比比较大 208 | if (sum > maxVariance) 209 | { 210 | maxVariance = sum; 211 | maxIndex = i; 212 | } 213 | } 214 | 215 | *sg = colors.at(maxIndex).toColor(); 216 | return true; 217 | } 218 | 219 | /** 220 | * 同上 221 | * 获取四种颜色:背景色、前景色(文字)、辅助背景色(与文字色差大)、辅助前景色(与背景色差大) 222 | */ 223 | bool ImageUtil::getBgFgSgColor(QList colors, QColor *bg, QColor *fg, QColor *sbg, QColor *sfg) 224 | { 225 | // 调用上面先获取背景色、前景色 226 | if (!getBgFgColor(colors, bg, fg)) 227 | return false; 228 | 229 | // 再获取合适的辅助色 230 | if (colors.size() == 2) 231 | { 232 | *sbg = getInvertColor(*fg); // 文字取反 233 | *sfg = getInvertColor(*bg); // 背景取反 234 | return false; 235 | } 236 | 237 | // 计算辅助背景色 238 | int maxIndex = -1; 239 | qint64 maxVariance = 0; 240 | for (int i = 0; i < colors.size(); i++) 241 | { 242 | ColorOctree::ColorCount c = colors.at(i); 243 | if (c.toColor() == *bg || c.toColor() == *fg) 244 | continue; 245 | 246 | int r = c.red, g = c.green, b = c.blue, n = c.count; 247 | qint64 variantBg = 3 * (r - bg->red()) * (r - bg->red()) 248 | + 4 * (g - bg->green()) * (g - bg->green()) 249 | + 2 * (b - bg->blue()) * (b - bg->blue()); 250 | qint64 variantFg = 3 * (r - fg->red()) * (r - fg->red()) 251 | + 4 * (g - fg->green()) * (g - fg->green()) 252 | + 2 * (b - fg->blue()) * (b - fg->blue()); 253 | 254 | qint64 variant = variantBg + variantFg * 2; // 文字占比比较大 255 | if (variant > maxVariance) 256 | { 257 | maxVariance = variant; 258 | maxIndex = i; 259 | } 260 | } 261 | *sbg = colors.at(maxIndex).toColor(); 262 | 263 | // 根据辅助背景色计算辅助前景色 264 | maxIndex = -1; 265 | maxVariance = 0; 266 | for (int i = 0; i < colors.size(); i++) 267 | { 268 | ColorOctree::ColorCount c = colors.at(i); 269 | if (c.toColor() == *sbg) 270 | continue; 271 | 272 | int r = c.red, g = c.green, b = c.blue, n = c.count; 273 | qint64 variant = 3 * (r - sbg->red()) * (r - sbg->red()) 274 | + 4 * (g - sbg->green()) * (g - sbg->green()) 275 | + 2 * (b - sbg->blue()) * (b - sbg->blue()); 276 | 277 | if (variant > maxVariance) 278 | { 279 | maxVariance = variant; 280 | maxIndex = i; 281 | } 282 | } 283 | *sfg = colors.at(maxIndex).toColor(); 284 | return true; 285 | } 286 | 287 | /// 获取色差最大的一项 288 | /// 由于RGB颜色空间不是均匀颜色空间,按照空间距离得到的色差并不完全符合人的视觉, 289 | /// 在实际应用时经常采取给各颜色分量加上一定权值的办法,一般加权取值(3,4,2) 290 | QColor ImageUtil::getFastestColor(QColor bg, QList palette) 291 | { 292 | qint64 maxi = -1; 293 | QColor maxiColor; 294 | int rr = bg.red(), gg = bg.green(), bb = bg.blue(); 295 | foreach (auto c, palette) 296 | { 297 | int r = c.red(), g = c.green(), b = c.blue(); 298 | qint64 delta = 3 * (r - rr) * (r - rr) 299 | + 4 * (g - gg) * (g - gg) 300 | + 2 * (b - bb) * (b - bb); 301 | if (delta > maxi) 302 | { 303 | maxi = delta; 304 | maxiColor = c; 305 | } 306 | } 307 | return maxiColor; 308 | } 309 | 310 | /// 获取色差最大的一项 311 | /// 但是也会参考数量,数量越多权重越高 312 | QColor ImageUtil::getFastestColor(QColor bg, QList palette, int enableCount) 313 | { 314 | qint64 maxi = -1; 315 | QColor maxiColor = QColor::Invalid; 316 | int rr = bg.red(), gg = bg.green(), bb = bg.blue(); 317 | foreach (auto c, palette) 318 | { 319 | int r = c.red, g = c.green, b = c.blue; 320 | qint64 delta = 3 * (r - rr) * (r - rr) 321 | + 4 * (g - gg) * (g - gg) 322 | + 2 * (b - bb) * (b - bb); 323 | if (enableCount == 1) 324 | delta *= c.count; 325 | else if (enableCount == 2) 326 | delta *= qint64(sqrt(c.count + 1)); 327 | if (delta > maxi) 328 | { 329 | maxi = delta; 330 | maxiColor = c.toColor(); 331 | } 332 | } 333 | return maxiColor; 334 | } 335 | 336 | /// 返回随机深色调 337 | QColor ImageUtil::randomColor() 338 | { 339 | return QColor::fromHsl(rand()%360,rand()%256,rand()%200); 340 | } 341 | 342 | 343 | /// 计算明度 344 | /// <40为暗色,>120为亮色 345 | double ImageUtil::calculateLuminance(QColor c) 346 | { 347 | return (0.2126 * c.redF() + 0.7152 * c.greenF() + 0.0722 * c.blueF()) * 255; 348 | } 349 | 350 | /// 从一组色彩中获取最相似的颜色 351 | QColor ImageUtil::getCloestColorByRGB(QColor color, const QList &colors) 352 | { 353 | double min_diff = std::numeric_limits::max(); 354 | QColor nearest_color = color; 355 | foreach (auto c, colors) 356 | { 357 | int diff = perceptualRgbDistance(color, c); 358 | if (diff < min_diff) 359 | { 360 | min_diff = diff; 361 | nearest_color = c; 362 | } 363 | } 364 | return nearest_color; 365 | } 366 | 367 | /* 查找视觉上最接近的颜色,CIELAB 颜色空间 368 | 颜色空间转换: 369 | - RGB → XYZ → CIELAB(使用 D65 白点,标准照明条件) 370 | - 包含 sRGB 非线性校正 371 | 色差计算: 372 | - 使用 CIEDE2000 色差公式(ΔE00) 373 | - 考虑了明度、色度和色调的感知差异 374 | - 包含了补偿因子(如色调旋转、彩度权重) 375 | 优势: 376 | - 更符合人眼对颜色的感知 377 | - 对高饱和度和中等明度的颜色特别准确 378 | - 正确处理了 CIELAB 空间中的非线性问题 379 | */ 380 | QColor ImageUtil::getVisuallyClosestColorByCIELAB(const QColor& target, const QList& colors, const QList& colorLabs) { 381 | QVector3D targetLab = rgbToLab(target); 382 | QColor closestColor; 383 | double minDeltaE = std::numeric_limits::max(); 384 | 385 | for (int i = 0; i < colors.size(); i++) { 386 | QVector3D colorLab = colorLabs[i]; 387 | double deltaE = deltaE94(targetLab, colorLab); 388 | 389 | if (deltaE < minDeltaE) { 390 | minDeltaE = deltaE; 391 | closestColor = colors[i]; 392 | } 393 | } 394 | 395 | return closestColor; 396 | } 397 | 398 | // RGB 转 XYZ (D65 白点) 399 | QVector3D ImageUtil::rgbToXyz(const QColor& color) { 400 | float r = color.redF(); 401 | float g = color.greenF(); 402 | float b = color.blueF(); 403 | 404 | // sRGB 到线性 RGB 的转换 405 | auto linearize = [](float c) { 406 | return c <= 0.04045 ? c / 12.92 : std::pow((c + 0.055) / 1.055, 2.4); 407 | }; 408 | 409 | r = linearize(r); 410 | g = linearize(g); 411 | b = linearize(b); 412 | 413 | // 线性 RGB 到 XYZ 的转换 (D65 白点) 414 | float X = r * 0.4124564 + g * 0.3575761 + b * 0.1804375; 415 | float Y = r * 0.2126729 + g * 0.7151522 + b * 0.0721750; 416 | float Z = r * 0.0193339 + g * 0.1191920 + b * 0.9503041; 417 | 418 | return {X, Y, Z}; 419 | } 420 | 421 | // XYZ 转 CIELAB (D65 白点) 422 | QVector3D ImageUtil::xyzToLab(const QVector3D& xyz) { 423 | float X = xyz.x() / 0.95047; // D65 白点 X 424 | float Y = xyz.y(); 425 | float Z = xyz.z() / 1.08883; // D65 白点 Z 426 | 427 | auto f = [](float t) { 428 | return t > 0.008856 ? std::cbrt(t) : (7.787 * t + 16.0 / 116.0); 429 | }; 430 | 431 | float L = 116.0 * f(Y) - 16.0; 432 | float a = 500.0 * (f(X) - f(Y)); 433 | float b = 200.0 * (f(Y) - f(Z)); 434 | 435 | return {L, a, b}; 436 | } 437 | 438 | // RGB 转 CIELAB 439 | QVector3D ImageUtil::rgbToLab(const QColor& color) { 440 | return xyzToLab(rgbToXyz(color)); 441 | } 442 | 443 | // 计算 CIELAB 颜色空间中的 Delta E 2000 距离 444 | double ImageUtil::deltaE2000(const QVector3D& lab1, const QVector3D& lab2) { 445 | double L1 = lab1.x(); 446 | double a1 = lab1.y(); 447 | double b1 = lab1.z(); 448 | double L2 = lab2.x(); 449 | double a2 = lab2.y(); 450 | double b2 = lab2.z(); 451 | 452 | // 计算 C1, C2, h1, h2 453 | double C1 = std::sqrt(a1 * a1 + b1 * b1); 454 | double C2 = std::sqrt(a2 * a2 + b2 * b2); 455 | double C_bar = (C1 + C2) / 2.0; 456 | 457 | // 计算 G 458 | double G = 0.5 * (1 - std::sqrt(std::pow(C_bar, 7) / (std::pow(C_bar, 7) + std::pow(25.0, 7)))); 459 | 460 | // 计算 a' 461 | double a1_prime = a1 * (1 + G); 462 | double a2_prime = a2 * (1 + G); 463 | 464 | // 计算 C', h' 465 | double C1_prime = std::sqrt(a1_prime * a1_prime + b1 * b1); 466 | double C2_prime = std::sqrt(a2_prime * a2_prime + b2 * b2); 467 | 468 | double h1_prime = std::atan2(b1, a1_prime); 469 | double h2_prime = std::atan2(b2, a2_prime); 470 | 471 | // 确保 h 在 0 到 2π 之间 472 | if (h1_prime < 0) h1_prime += 2 * M_PI; 473 | if (h2_prime < 0) h2_prime += 2 * M_PI; 474 | 475 | // 计算 ΔL', ΔC', Δh' 476 | double delta_L_prime = L2 - L1; 477 | double delta_C_prime = C2_prime - C1_prime; 478 | 479 | double delta_h_prime; 480 | if (C1_prime * C2_prime == 0) { 481 | delta_h_prime = 0; 482 | } else { 483 | double dh = h2_prime - h1_prime; 484 | if (std::abs(dh) <= M_PI) { 485 | delta_h_prime = dh; 486 | } else if (dh > M_PI) { 487 | delta_h_prime = dh - 2 * M_PI; 488 | } else { 489 | delta_h_prime = dh + 2 * M_PI; 490 | } 491 | } 492 | 493 | double delta_H_prime = 2 * std::sqrt(C1_prime * C2_prime) * std::sin(delta_h_prime / 2); 494 | 495 | // 计算 C' 平均值和 h' 平均值 496 | double C_prime_bar = (C1_prime + C2_prime) / 2.0; 497 | 498 | double h_prime_bar; 499 | if (C1_prime * C2_prime == 0) { 500 | h_prime_bar = h1_prime + h2_prime; 501 | } else { 502 | double dh = h2_prime - h1_prime; 503 | if (std::abs(dh) <= M_PI) { 504 | h_prime_bar = (h1_prime + h2_prime) / 2.0; 505 | } else { 506 | if (h1_prime + h2_prime < 2 * M_PI) { 507 | h_prime_bar = (h1_prime + h2_prime + 2 * M_PI) / 2.0; 508 | } else { 509 | h_prime_bar = (h1_prime + h2_prime - 2 * M_PI) / 2.0; 510 | } 511 | } 512 | } 513 | 514 | // 计算 T 515 | double T = 1 - 0.17 * std::cos(h_prime_bar - M_PI / 6) + 516 | 0.24 * std::cos(2 * h_prime_bar) + 517 | 0.32 * std::cos(3 * h_prime_bar + M_PI / 30) - 518 | 0.20 * std::cos(4 * h_prime_bar - 63 * M_PI / 180); 519 | 520 | // 计算 Δθ 521 | double delta_theta = 30 * M_PI / 180 * std::exp(-std::pow((h_prime_bar * 180 / M_PI - 275) / 25, 2)); 522 | 523 | // 计算 RC, SL, SC, SH 524 | double R_C = 2 * std::sqrt(std::pow(C_prime_bar, 7) / (std::pow(C_prime_bar, 7) + std::pow(25.0, 7))); 525 | double S_L = 1 + (0.015 * std::pow(L2 + L1 - 100, 2)) / std::sqrt(20 + std::pow(L2 + L1 - 100, 2)); 526 | double S_C = 1 + 0.045 * C_prime_bar; 527 | double S_H = 1 + 0.015 * C_prime_bar * T; 528 | 529 | // 计算 RT 530 | double R_T = -std::sin(2 * delta_theta) * R_C; 531 | 532 | // 计算最终的 ΔE00 533 | double delta_E = std::sqrt( 534 | std::pow(delta_L_prime / S_L, 2) + 535 | std::pow(delta_C_prime / S_C, 2) + 536 | std::pow(delta_H_prime / S_H, 2) + 537 | R_T * (delta_C_prime / S_C) * (delta_H_prime / S_H) 538 | ); 539 | 540 | return delta_E; 541 | } 542 | 543 | /// CIELAB 空间下的 CIE94 色差公式(比 CIEDE2000 快 3-5 倍) 544 | double ImageUtil::deltaE94(const QVector3D& lab1, const QVector3D& lab2) { 545 | double L1 = lab1.x(), a1 = lab1.y(), b1 = lab1.z(); 546 | double L2 = lab2.x(), a2 = lab2.y(), b2 = lab2.z(); 547 | 548 | double deltaL = L1 - L2; 549 | double C1 = std::sqrt(a1*a1 + b1*b1); 550 | double C2 = std::sqrt(a2*a2 + b2*b2); 551 | double deltaC = C1 - C2; 552 | double deltaA = a1 - a2; 553 | double deltaB = b1 - b2; 554 | double deltaH_squared = deltaA*deltaA + deltaB*deltaB - deltaC*deltaC; 555 | 556 | // 纺织品应用的参数(可根据需求调整) 557 | double kL = 1, kC = 1, kH = 1; 558 | double SL = 1, SC = 1 + 0.045 * C1; 559 | double SH = 1 + 0.015 * C1; 560 | 561 | return std::sqrt( 562 | std::pow(deltaL / (kL * SL), 2) + 563 | std::pow(deltaC / (kC * SC), 2) + 564 | std::pow(deltaH_squared > 0 ? std::sqrt(deltaH_squared) / (kH * SH) : 0, 2) 565 | ); 566 | } 567 | 568 | /// 感知加权的 RGB 距离(比 CIELAB 快 10 倍以上) 569 | double ImageUtil::perceptualRgbDistance(const QColor& c1, const QColor& c2) { 570 | int r1 = c1.red(), g1 = c1.green(), b1 = c1.blue(); 571 | int r2 = c2.red(), g2 = c2.green(), b2 = c2.blue(); 572 | 573 | // 中点位置 574 | int r_mean = (r1 + r2) / 2; 575 | 576 | // 加权欧几里得距离(更接近人眼感知) 577 | int dr = r1 - r2; 578 | int dg = g1 - g2; 579 | int db = b1 - b2; 580 | 581 | return std::sqrt( 582 | (2 + r_mean/256.0) * dr*dr + 583 | 4 * dg*dg + 584 | (2 + (255 - r_mean)/256.0) * db*db 585 | ); 586 | } 587 | --------------------------------------------------------------------------------