├── .gitignore ├── NeveMusic.pro ├── README.md ├── appicon.ico ├── color_octree ├── coloroctree.cpp ├── coloroctree.h ├── imageutil.cpp └── imageutil.h ├── facile_menu ├── facilemenu.cpp ├── facilemenu.h ├── facilemenuitem.cpp └── facilemenuitem.h ├── interactive_buttons ├── interactivebuttonbase.cpp └── interactivebuttonbase.h ├── main.cpp ├── order_player ├── clickslider.h ├── desktoplyricwidget.cpp ├── desktoplyricwidget.h ├── itemselectionlistview.h ├── logindialog.cpp ├── logindialog.h ├── logindialog.ui ├── lyricstreamwidget.h ├── numberanimation.cpp ├── numberanimation.h ├── orderplayerwindow.cpp ├── orderplayerwindow.h ├── orderplayerwindow.ui ├── roundedpixmaplabel.h └── songbeans.h ├── resource.rc ├── resources.qrc ├── resources ├── bg │ └── bg.png ├── icons │ ├── candy.png │ ├── egg.png │ ├── favorite.png │ ├── favorite0.png │ ├── heart.png │ ├── heart2.png │ ├── history.png │ ├── list_circle.png │ ├── lyric_hide.png │ ├── lyric_show.png │ ├── moon.png │ ├── mute.png │ ├── next_song.png │ ├── order.png │ ├── order_list.png │ ├── pause.png │ ├── play.png │ ├── playlist.png │ ├── random.png │ ├── random_circle.png │ ├── search.png │ ├── settings.png │ ├── single_circle.png │ ├── sub_menu_arrow.png │ └── volume.png ├── musics │ ├── QQ-music.png │ └── netease-cloud-music.png └── snow │ ├── neve_music.png │ ├── snow1.png │ ├── snow2.png │ ├── snow3.png │ ├── snow4.png │ ├── snow5.png │ └── snowman.png └── screenshots ├── custom.png ├── lists.png ├── music0.png ├── music1.png ├── music2.png ├── music3.png ├── music4.png ├── music5.png ├── music6.png └── switch.png /.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 | /.qmake.cache 35 | /.qmake.stash 36 | 37 | # qtcreator generated files 38 | *.pro.user* 39 | 40 | # xemacs temporary files 41 | *.flc 42 | 43 | # Vim temporary files 44 | .*.swp 45 | 46 | # Visual Studio generated files 47 | *.ib_pdb_index 48 | *.idb 49 | *.ilk 50 | *.pdb 51 | *.sln 52 | *.suo 53 | *.vcproj 54 | *vcproj.*.*.user 55 | *.ncb 56 | *.sdf 57 | *.opensdf 58 | *.vcxproj 59 | *vcxproj.* 60 | 61 | # MinGW generated files 62 | *.Debug 63 | *.Release 64 | 65 | # Python byte code 66 | *.pyc 67 | 68 | # Binaries 69 | # -------- 70 | *.dll 71 | *.exe 72 | 73 | -------------------------------------------------------------------------------- /NeveMusic.pro: -------------------------------------------------------------------------------- 1 | QT += core gui network multimedia 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 | RC_FILE += resource.rc 13 | 14 | # You can also make your code fail to compile if it uses deprecated APIs. 15 | # In order to do so, uncomment the following line. 16 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 17 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 18 | 19 | INCLUDEPATH += interactive_buttons/ \ 20 | facile_menu/ \ 21 | color_octree/ \ 22 | order_player/ 23 | 24 | SOURCES += \ 25 | color_octree/coloroctree.cpp \ 26 | color_octree/imageutil.cpp \ 27 | order_player/desktoplyricwidget.cpp \ 28 | facile_menu/facilemenu.cpp \ 29 | facile_menu/facilemenuitem.cpp \ 30 | interactive_buttons/interactivebuttonbase.cpp \ 31 | order_player/logindialog.cpp \ 32 | main.cpp \ 33 | order_player/numberanimation.cpp \ 34 | order_player/orderplayerwindow.cpp 35 | 36 | HEADERS += \ 37 | order_player/clickslider.h \ 38 | color_octree/coloroctree.h \ 39 | color_octree/imageutil.h \ 40 | order_player/desktoplyricwidget.h \ 41 | facile_menu/facilemenu.h \ 42 | facile_menu/facilemenuitem.h \ 43 | interactive_buttons/interactivebuttonbase.h \ 44 | order_player/itemselectionlistview.h \ 45 | order_player/logindialog.h \ 46 | order_player/lyricstreamwidget.h \ 47 | order_player/numberanimation.h \ 48 | order_player/orderplayerwindow.h \ 49 | order_player/roundedpixmaplabel.h \ 50 | order_player/songbeans.h \ 51 | resource.rc 52 | 53 | FORMS += \ 54 | order_player/logindialog.ui \ 55 | order_player/orderplayerwindow.ui 56 | 57 | # Default rules for deployment. 58 | qnx: target.path = /tmp/$${TARGET}/bin 59 | else: unix:!android: target.path = /opt/$${TARGET}/bin 60 | !isEmpty(target.path): INSTALLS += target 61 | 62 | RESOURCES += \ 63 | resources.qrc 64 | 65 | DISTFILES += \ 66 | README.md 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 雪以音乐 2 | === 3 | 4 | ——[神奇弹幕](https://github.com/iwxyi/Bilibili-MagicalDanmaku)点歌姬插件 5 | 6 | 同时支持网易云音乐、QQ音乐,自适应歌曲色调,自动点歌、批量操作,在没有版权的时候自动切换至另一音源。 7 | 8 | 可作为轻量级插件嵌入需要点歌的程序,通过信号槽点歌、接收点歌成功通知。 9 | 10 | ## 功能 11 | 12 | - 音源自动切换 13 | - 按关键词搜索 14 | - 智能分析歌名 15 | - 点歌自动搜索 16 | - 点歌播放队列 17 | - 固定播放队列 18 | - 点歌防止刷屏 19 | - 预计时长回复 20 | - 背景封面模糊 21 | - 提取主题色调 22 | - 查看桌面歌词 23 | - 收藏夹与历史 24 | - 一键导入歌单 25 | - 网易会员登录 26 | - 调整歌词时间 27 | 28 | ## 详细介绍 29 | 30 | ### 点歌技巧 31 | 32 | 通过[神奇弹幕](https://github.com/iwxyi/Bilibili-MagicalDanmaku)点歌的歌曲,会在雪以音乐(模块)中自动搜索,智能挑选最合适的歌曲,添加到点歌列表。 33 | 34 | #### 自动换源 35 | 36 | 目前支持网易云音乐、QQ音乐。若播放的歌曲在当前音源没有版权,**自动切换至另一音源** 37 | 38 | #### 会员歌曲 39 | 40 | 已支持网易云音乐的平台账号登录,若是VIP会员,可播放VIP歌曲 41 | 42 | #### 指定歌手 43 | 44 | 通过 “**点歌 歌名 歌手**” 或“**点歌 歌名-歌手**”的形式,指定该歌曲的歌手(若未找到,则播放第一首歌)。 45 | 46 | #### 换这首歌 47 | 48 | 当播放的歌曲不是想点的歌时,可在搜索结果的右键菜单中,选择“换这首歌”,立刻替换为新的歌曲,自动删掉不想要的歌。 49 | 50 | > 若不是通过点歌自动搜索的结果,将不会显示“点歌用户”和“换这首歌”菜单 51 | 52 | ![switch](screenshots/switch.png) 53 | 54 | #### 试听歌曲 55 | 56 | 在菜单中开启“试听接口”,可**试听QQ音乐会员歌曲一分钟**;网易云默认开启试听 57 | 58 | ### 点歌与固定播放 59 | 60 | ![列表](screenshots/lists.png) 61 | 62 | 左上角四个tab,分别是: 63 | 64 | - 点歌:按点歌时间自动添加的歌曲,自动播放 65 | - 固定歌曲:点歌播放结束后,将随机播放固定歌曲(建议导入歌单到这里) 66 | - 收藏:手动添加到收藏,快速播放 67 | - 历史:所有歌曲的播放历史 68 | 69 | 所有列表均支持**任意多选**操作。 70 | 71 | ### 导入歌单链接 72 | 73 | 在大部分歌曲列表的右键菜单,可找到“打开分享的歌单”,输入网易云音乐、QQ音乐的歌单链接,显示歌单中的所有歌曲。与搜索结果一样,可任意多选播放、点歌、收藏等等。 74 | 75 | > 未测试超过1000首歌的歌单,可能显示不全 76 | 77 | ### 自定义回复 78 | 79 | 监听**神奇弹幕**的事件,可自定义点歌成功、未带勋章、频繁点歌等情况下的**自定回复文本与冷却时间**。 80 | 81 | ![自定义回复](screenshots/custom.png) 82 | 83 | ## 截图预览 84 | 85 | ![music0](screenshots/music0.png) 86 | 87 | ![music3](screenshots/music3.png) 88 | 89 | ![music1](screenshots/music1.png) 90 | 91 | ![music5](screenshots/music5.png) 92 | 93 | ![music2](screenshots/music2.png) 94 | 95 | ![music4](screenshots/music4.png) 96 | 97 | ![music6](screenshots/music6.png) 98 | 99 | ## 特别鸣谢 100 | 101 | - 我自己: 102 | - [InteractiveButtons](https://github.com/iwxyi/Qt-InteractiveButtons) 103 | - [FacileMenu](https://github.com/iwxyi/Qt-FacileMenu) 104 | - [ImageThemeExtraion](https://github.com/iwxyi/ImageThemeExtraction) 105 | 106 | - [Binaryify/NeteaseCloudMusicApi](https://github.com/Binaryify/NeteaseCloudMusicApi) 107 | - [rain120/qq-music-api](https://github.com/rain120/qq-music-api) 108 | -------------------------------------------------------------------------------- /appicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/appicon.ico -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 = 20); 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.cpp: -------------------------------------------------------------------------------- 1 | #include "imageutil.h" 2 | 3 | /** 4 | * 图片转换为颜色集 5 | */ 6 | QColor ImageUtil::getImageAverageColor(QImage image, int maxSize) 7 | { 8 | int hCount = image.width(); 9 | int vCount = image.height(); 10 | 11 | if (hCount > maxSize || vCount > maxSize) // 数量过大,按比例缩减 12 | { 13 | double prop = (double)maxSize / qMax(hCount, vCount); 14 | image.scaledToWidth(image.width() * prop); // 缩放到最大大小 15 | } 16 | 17 | long long sumr = 0, sumg = 0, sumb = 0; 18 | 19 | int w = image.width(), h = image.height(); 20 | int sumSize = w * h; 21 | for (int y = 0; y < h; y++) 22 | { 23 | QRgb *line = (QRgb *)image.scanLine(y); 24 | for (int x = 0; x < w; x++) 25 | { 26 | int r = qRed(line[x]), g = qGreen(line[x]), b = qBlue(line[x]); 27 | sumr += r; 28 | sumg += g; 29 | sumb += b; 30 | } 31 | } 32 | 33 | sumr /= sumSize; 34 | sumg /= sumSize; 35 | sumb /= sumSize; 36 | return QColor(sumr, sumg, sumb); 37 | } 38 | 39 | /** 40 | * 获取图片中所有的颜色 41 | */ 42 | QList ImageUtil::extractImageThemeColors(QImage image, int count) 43 | { 44 | auto octree = ColorOctree(image, IMAGE_CALC_PIXEL_MAX_SIZE, count); 45 | auto result = octree.result(); 46 | 47 | if (!result.size() || result.first().count <= 0) 48 | return result; 49 | 50 | // 可以过滤太少的颜色,看情况开关 51 | int maxCount = result.first().count; 52 | int minCount = maxCount / 1000; // 小于最大项1‰的都去掉 53 | while (result.last().count < minCount) 54 | result.removeLast(); 55 | 56 | return result; 57 | } 58 | 59 | /** 60 | * 获取图片中的所有主题色 61 | * 使用最小差值法一一求差 62 | * 然后和调色盘中颜色对比 63 | * 取出每种最是相近的颜色 64 | */ 65 | QList ImageUtil::extractImageThemeColorsInPalette(QImage image, QList paletteColors, int needCount) 66 | { 67 | auto octree = ColorOctree(image, IMAGE_CALC_PIXEL_MAX_SIZE, paletteColors.size()); 68 | auto result = octree.result(); 69 | 70 | QList colors; 71 | for (int i = 0; i < result.size(); i++) 72 | { 73 | auto cc = result.at(i); 74 | QColor nestColor; 75 | int minVariance = 255*255*3+1; 76 | for (int j = 0; j < paletteColors.size(); j++) 77 | { 78 | auto qc = paletteColors.at(j); 79 | 80 | // 差值计算:直接计算方差吧 81 | int dr = cc.red - qc.red(); 82 | int dg = cc.green - qc.green(); 83 | int db = cc.blue - qc.blue(); 84 | int variance = dr * dr + dg * dg + db * db; 85 | 86 | if (minVariance > variance) 87 | { 88 | minVariance = variance; 89 | nestColor = qc; 90 | } 91 | } 92 | // 找到方差最小的颜色 93 | colors.append(nestColor); 94 | } 95 | 96 | return colors; 97 | } 98 | 99 | /** 100 | * 获取反色 101 | */ 102 | QColor ImageUtil::getInvertColor(QColor color) 103 | { 104 | auto getInvert = [=](int c) -> int{ 105 | if (c < 96 || c > 160) 106 | return 255 - c; 107 | else if (c < 128) 108 | return 255; 109 | else 110 | return 0; 111 | }; 112 | 113 | color.setRed(getInvert(color.red())); 114 | color.setGreen(getInvert(color.green())); 115 | color.setBlue(getInvert(color.blue())); 116 | return color; 117 | } 118 | 119 | /** 120 | * 获取一张图片中应有的背景色、前景色 121 | * 背景色是颜色占比最高的 122 | * 前景色是与其余颜色的(色差*数量)方差和最大的 123 | * @return 颜色提取是否成功 124 | */ 125 | bool ImageUtil::getBgFgColor(QList colors, QColor *bg, QColor *fg) 126 | { 127 | Q_ASSERT(bg && fg); 128 | if (!colors.size()) 129 | { 130 | *bg = Qt::white; 131 | *fg = Qt::black; 132 | return false; 133 | } 134 | 135 | if (colors.size() == 1) 136 | { 137 | *bg = colors.first().toColor(); 138 | *fg = getInvertColor(*bg); 139 | return false; 140 | } 141 | 142 | int maxIndex = -1; 143 | qint64 maxVariance = 0; 144 | int size = colors.size(); 145 | for (int i = 0; i < size; i++) 146 | { 147 | ColorOctree::ColorCount c = colors.at(i); 148 | int r = c.red, g = c.green, b = c.blue, n = c.count; 149 | 150 | qint64 sumVariance = 0; 151 | for (int j = 0; j < size; j++) 152 | { 153 | if (j == i) 154 | continue; 155 | ColorOctree::ColorCount c2 = colors.at(j); 156 | qint64 variant = (r - c2.red) * (r - c2.red) 157 | + (g - c2.green) * (g - c2.green) 158 | + (b - c2.blue) * (b - c2.blue); 159 | variant *= c2.count; 160 | sumVariance += variant; 161 | } 162 | 163 | if (sumVariance > maxVariance) 164 | { 165 | maxVariance = sumVariance; 166 | maxIndex = i; 167 | } 168 | } 169 | 170 | *bg = colors.first().toColor(); 171 | *fg = colors.at(maxIndex).toColor(); 172 | return true; 173 | } 174 | 175 | /** 176 | * 同上,外加一个辅助色 177 | */ 178 | bool ImageUtil::getBgFgSgColor(QList colors, QColor *bg, QColor *fg, QColor *sg) 179 | { 180 | // 调用上面先获取背景色、前景色 181 | if (!getBgFgColor(colors, bg, fg)) 182 | return false; 183 | 184 | // 再获取合适的辅助色 185 | if (colors.size() == 2) 186 | { 187 | *sg = getInvertColor(*fg); // 文字取反 188 | return false; 189 | } 190 | 191 | int maxIndex = -1; 192 | qint64 maxVariance = 0; 193 | for (int i = 0; i < colors.size(); i++) 194 | { 195 | ColorOctree::ColorCount c = colors.at(i); 196 | if (c.toColor() == *bg || c.toColor() == *fg) 197 | continue; 198 | 199 | int r = c.red, g = c.green, b = c.blue, n = c.count; 200 | qint64 variantBg = (r - bg->red()) * (r - bg->red()) 201 | + (g - bg->green()) * (g - bg->green()) 202 | + (b - bg->blue()) * (b - bg->blue()); 203 | qint64 variantFg = (r - fg->red()) * (r - fg->red()) 204 | + (g - fg->green()) * (g - fg->green()) 205 | + (b - fg->blue()) * (b - fg->blue()); 206 | qint64 sum = variantBg + variantFg * 2; // 文字占比比较大 207 | if (sum > maxVariance) 208 | { 209 | maxVariance = sum; 210 | maxIndex = i; 211 | } 212 | } 213 | 214 | *sg = colors.at(maxIndex).toColor(); 215 | return true; 216 | } 217 | 218 | /** 219 | * 同上 220 | * 获取四种颜色:背景色、前景色(文字)、辅助背景色(与文字色差大)、辅助前景色(与背景色差大) 221 | */ 222 | bool ImageUtil::getBgFgSgColor(QList colors, QColor *bg, QColor *fg, QColor *sbg, QColor *sfg) 223 | { 224 | // 调用上面先获取背景色、前景色 225 | if (!getBgFgColor(colors, bg, fg)) 226 | return false; 227 | 228 | // 再获取合适的辅助色 229 | if (colors.size() == 2) 230 | { 231 | *sbg = getInvertColor(*fg); // 文字取反 232 | *sfg = getInvertColor(*bg); // 背景取反 233 | return false; 234 | } 235 | 236 | int maxBIndex = -1, maxFIndex = -1; 237 | qint64 maxBVariance = 0, maxFVariance = 0; 238 | for (int i = 0; i < colors.size(); i++) 239 | { 240 | ColorOctree::ColorCount c = colors.at(i); 241 | if (c.toColor() == *bg || c.toColor() == *fg) 242 | continue; 243 | 244 | int r = c.red, g = c.green, b = c.blue, n = c.count; 245 | qint64 variantBg = (r - bg->red()) * (r - bg->red()) 246 | + (g - bg->green()) * (g - bg->green()) 247 | + (b - bg->blue()) * (b - bg->blue()); 248 | qint64 variantFg = (r - fg->red()) * (r - fg->red()) 249 | + (g - fg->green()) * (g - fg->green()) 250 | + (b - fg->blue()) * (b - fg->blue()); 251 | 252 | qint64 sum = variantBg + variantFg * 2; // 文字占比比较大 253 | if (sum > maxBVariance) 254 | { 255 | maxBVariance = sum; 256 | maxBIndex = i; 257 | } 258 | 259 | sum = variantBg * 2 + variantFg; // 背景占比比较大 260 | if (sum > maxFVariance) 261 | { 262 | maxFVariance = sum; 263 | maxFIndex = i; 264 | } 265 | } 266 | 267 | *sbg = colors.at(maxBIndex).toColor(); 268 | *sfg = colors.at(maxFIndex).toColor(); 269 | return true; 270 | } 271 | -------------------------------------------------------------------------------- /color_octree/imageutil.h: -------------------------------------------------------------------------------- 1 | #ifndef PIXMAPUTIL_H 2 | #define PIXMAPUTIL_H 3 | 4 | #include 5 | #include 6 | #include "coloroctree.h" 7 | 8 | #define IMAGE_CALC_PIXEL_MAX_SIZE 128 // 计算的最大边长(大图缩小) 9 | 10 | class ImageUtil 11 | { 12 | public: 13 | static QColor getImageAverageColor(QImage image, int maxPool = IMAGE_CALC_PIXEL_MAX_SIZE); 14 | 15 | static QList extractImageThemeColors(QImage image, int count); 16 | 17 | static QList extractImageThemeColorsInPalette(QImage image, QList paletteColors, int needCount); 18 | 19 | static QColor getInvertColor(QColor color); 20 | 21 | static bool getBgFgColor(QList colors, QColor *bg, QColor *fg); 22 | 23 | static bool getBgFgSgColor(QList colors, QColor *bg, QColor *fg, QColor *sg); 24 | 25 | static bool getBgFgSgColor(QList colors, QColor *bg, QColor *fg, QColor *sbg, QColor *sfg); 26 | }; 27 | 28 | #endif // PIXMAPUTIL_H 29 | -------------------------------------------------------------------------------- /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 16 | #include "facilemenuitem.h" 17 | 18 | #define DEFAULT_MENU_BLUR_ALPHA 33 19 | 20 | typedef std::function const FuncCheckType; 21 | 22 | class FacileMenu : public QWidget 23 | { 24 | Q_OBJECT 25 | public: 26 | FacileMenu(QWidget *parent = nullptr); 27 | ~FacileMenu() override; 28 | 29 | FacileMenuItem* addAction(QIcon icon, QString text, FuncType clicked = nullptr); 30 | FacileMenuItem* addAction(QIcon icon, FuncType clicked = nullptr); 31 | FacileMenuItem* addAction(QString text, FuncType clicked = nullptr); 32 | FacileMenuItem* addAction(QAction* action, bool deleteWithMenu = false); 33 | FacileMenuItem* addAction(QIcon icon, QString text, void (*func)()); 34 | template 35 | FacileMenuItem* addAction(QIcon icon, QString text, T *obj, void (T::*func)()); 36 | FacileMenu* addNumberedActions(QString pattern, int numberStart, int numberEnd, FuncItemType config = nullptr, FuncIntType clicked = nullptr); 37 | FacileMenu* addNumberedActions(QString pattern, int numberStart, int numberEnd, FuncItemIntType config, FuncIntType clicked = nullptr); 38 | FacileMenu* addActions(QList actions); 39 | 40 | FacileMenu* addRow(FuncType addActions = []{}); 41 | FacileMenu* beginRow(); 42 | FacileMenu* endRow(); 43 | QVBoxLayout* createNextColumn(); 44 | QBoxLayout* currentLayout() const; 45 | 46 | FacileMenu* addMenu(QIcon icon, QString text, FuncType clicked = nullptr); 47 | FacileMenu* addMenu(QString text, FuncType clicked = nullptr); 48 | FacileMenu* addMenu(QMenu* menu); 49 | FacileMenuItem* parentAction(); 50 | FacileMenuItem* lastAction(); 51 | FacileMenuItem* currentAction(); 52 | 53 | FacileMenu* addLayout(QLayout *layout, int stretch = 0); 54 | FacileMenu* addLayoutItem(QLayoutItem *item); 55 | FacileMenu* addSpacerItem(QSpacerItem *spacerItem); 56 | FacileMenu* addSpacing(int size); 57 | FacileMenu* addStretch(int stretch = 0); 58 | FacileMenu* addStrut(int size); 59 | FacileMenu* addWidget(QWidget *widget, int stretch = 0, Qt::Alignment alignment = Qt::Alignment()); 60 | FacileMenu* setSpacing(int spacing); 61 | FacileMenu* setStretchFactor(QWidget *widget, int stretch); 62 | FacileMenu* setStretchFactor(QLayout *layout, int stretch); 63 | 64 | FacileMenuItem* addSeparator(); 65 | FacileMenu* split(); 66 | FacileMenuItem* lastAddedItem(); 67 | 68 | int indexOf(FacileMenuItem* item); 69 | FacileMenuItem* at(int index); 70 | 71 | void exec(QPoint pos = QPoint(-1, -1)); 72 | void exec(QRect expt, bool vertical = false, QPoint pos = QPoint(-1, -1)); 73 | void execute(); 74 | void toHide(int focusIndex = -1); 75 | FacileMenu* finished(FuncType func); 76 | 77 | FacileMenu* addOptions(QListtexts, QListstates, FuncIntType clicked); 78 | FacileMenu* addOptions(QListtexts, int select, FuncIntType clicked); 79 | FacileMenu* singleCheck(FacileMenuItem* item); 80 | FacileMenu* uncheckAll(FacileMenuItem* except = nullptr, int begin = -1, int end = -1); 81 | QList checkedItems(); 82 | QList checkedIndexes(); 83 | QStringList checkedItemTexts(); 84 | QList checkedItemDatas(); 85 | FacileMenu* setSingleCheck(FuncCheckType clicked = nullptr); 86 | FacileMenu* setMultiCheck(FuncCheckType clicked = nullptr); 87 | 88 | FacileMenu* setTipArea(int x = 48); 89 | FacileMenu* setTipArea(QString longestTip); 90 | FacileMenu* setSplitInRow(bool split = true); 91 | 92 | signals: 93 | void signalActionTriggered(FacileMenuItem* action); 94 | void signalHidden(); // 只是隐藏了自己 95 | 96 | private slots: 97 | void itemMouseEntered(FacileMenuItem* item); 98 | 99 | protected: 100 | FacileMenu(bool sub, QWidget* parent = nullptr); 101 | FacileMenuItem* createMenuItem(QIcon icon, QString text); 102 | Qt::Key getShortcutByText(QString text) const; 103 | void setActionButton(InteractiveButtonBase* btn, bool isChip = false); 104 | void showSubMenu(FacileMenuItem* item); 105 | bool isCursorInArea(QPoint pos, FacileMenu* child = nullptr); 106 | void setKeyBoardUsed(bool use = true); 107 | bool isSubMenu() const; 108 | FacileMenuItem* addVSeparator(); 109 | void startAnimationOnShowed(); 110 | void startAnimationOnHidden(int focusIndex); 111 | 112 | void hideEvent(QHideEvent *event) override; 113 | void mouseMoveEvent(QMouseEvent *event) override; 114 | void keyPressEvent(QKeyEvent *event) override; 115 | void paintEvent(QPaintEvent *event) override; 116 | 117 | public: 118 | static void setColors(QColor normal, QColor hover, QColor press, QColor text); 119 | static QColor normal_bg; // 普通背景(用作全局是为了方便设置) 120 | static QColor hover_bg; // 悬浮背景 121 | static QColor press_bg; // 按下背景 122 | static QColor text_fg; // 字体/变色图标颜色 123 | bool hidden_by_another = false; // 是否是被要显示的另一个子菜单替换了。若否,隐藏全部菜单 124 | static int blur_bg_alpha; // 背景图显示程度,0禁用,1~100为模糊透明度 125 | 126 | private: 127 | QList items; 128 | QList v_separators, h_separators; 129 | QList other_widgets; // 手动添加的widget 130 | QHBoxLayout* main_hlayout; 131 | QVBoxLayout* main_vlayout; 132 | QList row_hlayouts; 133 | QList import_actions; 134 | QPixmap bg_pixmap; 135 | 136 | FacileMenu* current_sub_menu = nullptr; // 当前打开(不一定显示)的子菜单 137 | FacileMenu* parent_menu = nullptr; // 父对象的菜单 138 | FacileMenuItem* last_added_item = nullptr; // 最后添加的item 139 | FuncType* finished_func = nullptr; 140 | 141 | int addin_tip_area = 0; // 右边用来显示提示文字的区域 142 | bool adding_horizone = false; // 是否正在添加横向菜单 143 | bool align_mid_if_alone = false; // 是否居中对齐,如果只有icon或text 144 | bool linger_on_submenu_clicked = false; // 子菜单点击后,父菜单是否逐级隐藏(注意子菜单若有选择项需手动改变) 145 | bool _showing_animation = false; 146 | int current_index = -1; // 当前索引 147 | bool using_keyboard = false; // 是否正在使用键盘挑选菜单 148 | bool split_in_row = false; // 同一行是否默认添加分割线 149 | QRect window_rect; 150 | int window_height = 0; // 窗口高度,每次打开都更新一次 151 | }; 152 | 153 | #endif // FACILEMENU_H 154 | -------------------------------------------------------------------------------- /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::tip(QString sc) 101 | { 102 | shortcut_tip = sc; 103 | return this; 104 | } 105 | 106 | FacileMenuItem *FacileMenuItem::tip(bool exp, QString sc) 107 | { 108 | if (exp) 109 | tip(sc); 110 | return this; 111 | } 112 | 113 | FacileMenuItem *FacileMenuItem::tooltip(QString tt) 114 | { 115 | setToolTip(tt); 116 | return this; 117 | } 118 | 119 | FacileMenuItem *FacileMenuItem::tooltip(bool exp, QString tt) 120 | { 121 | if (exp) 122 | tooltip(tt); 123 | return this; 124 | } 125 | 126 | FacileMenuItem *FacileMenuItem::triggered(FuncType func) 127 | { 128 | connect(this, &InteractiveButtonBase::clicked, this, [=]{ 129 | func(); 130 | }); 131 | return this; 132 | } 133 | 134 | FacileMenuItem *FacileMenuItem::triggered(bool exp, FuncType func) 135 | { 136 | if (!exp) 137 | triggered(func); 138 | return this; 139 | } 140 | 141 | FacileMenuItem *FacileMenuItem::disable(bool exp) 142 | { 143 | if (exp) 144 | setDisabled(true); 145 | return this; 146 | } 147 | 148 | FacileMenuItem *FacileMenuItem::enable(bool exp) 149 | { 150 | if (exp) 151 | setEnabled(true); 152 | return this; 153 | } 154 | 155 | FacileMenuItem *FacileMenuItem::hide(bool exp) 156 | { 157 | if (exp) 158 | InteractiveButtonBase::hide(); 159 | return this; 160 | } 161 | 162 | /** 163 | * 默认就是show状态 164 | * 为了和show区分开 165 | */ 166 | FacileMenuItem *FacileMenuItem::visible(bool exp) 167 | { 168 | if (exp) 169 | InteractiveButtonBase::setVisible(true); 170 | return this; 171 | } 172 | 173 | FacileMenuItem *FacileMenuItem::check(bool exp) 174 | { 175 | setCheckable(true); 176 | if (exp) 177 | setChecked(true); 178 | else if (InteractiveButtonBase::icon.isNull()) 179 | model = IconText; // 强制显示check空白部分 180 | return this; 181 | } 182 | 183 | FacileMenuItem *FacileMenuItem::uncheck(bool exp) 184 | { 185 | setCheckable(true); 186 | if (exp) 187 | setChecked(false); 188 | return this; 189 | } 190 | 191 | /** 192 | * 切换状态 193 | * 如果选中了,则取消选中;反之亦然 194 | * (本来打算不只是选中状态,然而还没想到其他有什么能切换的) 195 | */ 196 | FacileMenuItem *FacileMenuItem::toggle(bool exp) 197 | { 198 | if (!exp) 199 | return this; 200 | if (isCheckable()) 201 | { 202 | setChecked(!isChecked()); 203 | } 204 | // 以后什么功能想到再加 205 | return this; 206 | } 207 | 208 | /** 209 | * 点击自动切换状态 210 | * 小心点,因为信号槽顺序的关系,若放在triggered后面,可能会出现相反的check 211 | * 建议只用于和顺序无关的自动切换 212 | * (还不知道怎么修改信号槽的调用顺序) 213 | */ 214 | FacileMenuItem *FacileMenuItem::autoToggle() 215 | { 216 | connect(this, &InteractiveButtonBase::clicked, this, [=]{ 217 | toggle(); 218 | }); 219 | return this; 220 | } 221 | 222 | FacileMenuItem *FacileMenuItem::text(bool exp, QString str) 223 | { 224 | if (exp) 225 | { 226 | // 去掉快捷键符号 227 | // 注意:这里设置文字不会改变原来的快捷键! 228 | setText(str.replace(QRegExp("&([\\w\\d])\\b"), "\\1")); 229 | // 调整大小 230 | setFixedForeSize(); 231 | } 232 | return this; 233 | } 234 | 235 | /** 236 | * 设置字符串,成立时 tru,不成立时 fal 237 | * 注意:这里是直接设置完整的文字,不会去掉快捷键&符号 238 | */ 239 | FacileMenuItem *FacileMenuItem::text(bool exp, QString tru, QString fal) 240 | { 241 | if (exp) 242 | setText(tru); 243 | else 244 | setText(fal); 245 | return this; 246 | } 247 | 248 | FacileMenuItem *FacileMenuItem::fgColor(QColor color) 249 | { 250 | setTextColor(color); 251 | return this; 252 | } 253 | 254 | FacileMenuItem *FacileMenuItem::fgColor(bool exp, QColor color) 255 | { 256 | if (exp) 257 | return fgColor(color); 258 | return this; 259 | } 260 | 261 | FacileMenuItem *FacileMenuItem::bgColor(QColor color) 262 | { 263 | setBgColor(color); 264 | return this; 265 | } 266 | 267 | FacileMenuItem *FacileMenuItem::bgColor(bool exp, QColor color) 268 | { 269 | if (exp) 270 | bgColor(color); 271 | return this; 272 | } 273 | 274 | /** 275 | * 满足条件时,text添加前缀 276 | */ 277 | FacileMenuItem *FacileMenuItem::prefix(bool exp, QString pfix) 278 | { 279 | if (exp) 280 | prefix(pfix); 281 | return this; 282 | } 283 | 284 | /** 285 | * 满足条件时,text添加后缀 286 | * @param inLeftParenthesis 支持 text(xxx) 形式,会在左括号前添加后缀 287 | */ 288 | FacileMenuItem *FacileMenuItem::suffix(bool exp, QString sfix, bool inLeftParenthesis) 289 | { 290 | if (exp) 291 | { 292 | suffix(sfix, inLeftParenthesis); 293 | } 294 | return this; 295 | } 296 | 297 | FacileMenuItem *FacileMenuItem::prefix(QString pfix) 298 | { 299 | setText(pfix + getText()); 300 | return this; 301 | } 302 | 303 | FacileMenuItem *FacileMenuItem::suffix(QString sfix, bool inLeftParenthesis) 304 | { 305 | if (!inLeftParenthesis) 306 | { 307 | setText(getText() + sfix); 308 | } 309 | else 310 | { 311 | QString text = getText(); 312 | int index = -1; 313 | if ((index = text.lastIndexOf("(")) > -1) 314 | { 315 | while (index > 0 && text.mid(index-1, 1) == " ") 316 | index--; 317 | } 318 | if (index <= 0) // 没有左括号或者以空格开头,直接加到最后面 319 | { 320 | setText(getText() + sfix); 321 | } 322 | else 323 | { 324 | setText(text.left(index) + sfix + text.right(text.length()-index)); 325 | } 326 | } 327 | return this; 328 | } 329 | 330 | FacileMenuItem *FacileMenuItem::icon(bool exp, QIcon ico) 331 | { 332 | if (exp) 333 | setIcon(ico); 334 | return this; 335 | } 336 | 337 | FacileMenuItem *FacileMenuItem::borderR(int radius, QColor co) 338 | { 339 | setRadius(radius); 340 | if (co != Qt::transparent) 341 | setBorderColor(co); 342 | else 343 | setBorderColor(press_bg); 344 | return this; 345 | } 346 | 347 | FacileMenuItem *FacileMenuItem::linger() 348 | { 349 | trigger_linger = true; 350 | return this; 351 | } 352 | 353 | /** 354 | * 绑定某一布尔类型的变量(只能全局变量) 355 | * 点击即切换值 356 | * 注意:因为是异步的,局部变量会导致崩溃! 357 | */ 358 | FacileMenuItem *FacileMenuItem::bind(bool &val) 359 | { 360 | connect(this, &InteractiveButtonBase::clicked, this, [&]{ 361 | val = !val; 362 | }); 363 | return this; 364 | } 365 | 366 | /** 367 | * 短期长按效果 368 | * 该操作不会影响其它任何交互效果 369 | * 即不会隐藏菜单,也不会解除单击信号 370 | */ 371 | FacileMenuItem *FacileMenuItem::longPress(FuncType func) 372 | { 373 | connect(this, &InteractiveButtonBase::signalMousePressLater, this, [=](QMouseEvent*){ 374 | func(); 375 | }); 376 | return this; 377 | } 378 | 379 | /** 380 | * 适用于连续设置 381 | * 当 iff 成立时继续 382 | * 否则取消后面所有设置 383 | */ 384 | FacileMenuItem *FacileMenuItem::ifer(bool exp) 385 | { 386 | if (exp) 387 | return this; 388 | 389 | // 返回一个无用item,在自己delete时也delete掉 390 | return createTempItem(); 391 | } 392 | 393 | /** 394 | * 完全等于 ifer 395 | * 如果已经在 ifer 里面,则先退出 396 | */ 397 | FacileMenuItem *FacileMenuItem::elifer(bool exp) 398 | { 399 | if (parent_menu_item_in_if) // ifer 不成立后的,退出并转至新的 ifer 400 | return parent_menu_item_in_if->ifer(exp); 401 | return ifer(exp); // 直接使用,完全等同于 ifer 402 | } 403 | 404 | FacileMenuItem *FacileMenuItem::elser() 405 | { 406 | if (parent_menu_item_in_if) 407 | return parent_menu_item_in_if; 408 | return createTempItem(); 409 | } 410 | 411 | /** 412 | * 适用于连续设置action时,满足条件则退出 413 | * 相当于一个控制语句 414 | * 当ex成立时,取消后面所有设置 415 | */ 416 | FacileMenuItem *FacileMenuItem::exiter(bool exp) 417 | { 418 | if (!exp) 419 | return this; 420 | 421 | // 返回一个无用item,在自己delete时也delete掉 422 | return createTempItem(false); 423 | } 424 | 425 | /** 426 | * 适用于连续设置 427 | * 满足某一条件则执行 func(this) 428 | */ 429 | FacileMenuItem *FacileMenuItem::ifer(bool exp, FuncItemType func, FuncItemType elseFunc) 430 | { 431 | if (exp) 432 | { 433 | if (func) 434 | func(this); 435 | } 436 | else 437 | { 438 | if (elseFunc) 439 | elseFunc(this); 440 | } 441 | return this; 442 | } 443 | 444 | /** 445 | * 适用于连续设置 446 | * 类似 switch 语句,输入判断的值 447 | * 当后续的 caser 满足 value 时,允许执行 caser 的 func 或后面紧跟着的的设置 448 | */ 449 | FacileMenuItem *FacileMenuItem::switcher(int value) 450 | { 451 | switch_value = value; 452 | switch_matched = false; 453 | return this; 454 | } 455 | 456 | /** 457 | * 当 value 等同于 switcher 判断的 value 时,执行 func 458 | * 并返回原始 item 459 | * 注意与重载的 caser(int) 进行区分 460 | */ 461 | FacileMenuItem *FacileMenuItem::caser(int value, FuncType func) 462 | { 463 | if (value == switch_value) 464 | { 465 | switch_matched = true; 466 | if (func) 467 | func(); 468 | } 469 | return this; 470 | } 471 | 472 | /** 473 | * 当 value 等同于 switcher 的 value 时,返回原始 item 474 | * 即执行 caser 后面的设置,直至 breaker 475 | * 注意与重载的 caser(int FuncType) 进行区分 476 | */ 477 | FacileMenuItem *FacileMenuItem::caser(int value) 478 | { 479 | // 可能已经接着一个没有 breaker 的 caser 480 | // 则回到上一级(这样会导致无法嵌套) 481 | if (this->parent_menu_item_in_if) 482 | { 483 | // 接着一个 !=的caser 后面 484 | if (value == parent_menu_item_in_if->switch_value) 485 | { 486 | parent_menu_item_in_if->switch_matched = true; 487 | return parent_menu_item_in_if; // 真正需要使用的实例 488 | } 489 | return this; // 继续使用自己(一个临时实例) 490 | } 491 | else // 自己是第一个 caser 或者 ==的caser 后面 492 | { 493 | if (value == switch_value) 494 | { 495 | switch_matched = true; 496 | return this; 497 | } 498 | return createTempItem(); 499 | } 500 | } 501 | 502 | /** 503 | * caser 的 value 不等于 switcher 的 value 时 504 | * 此语句用来退出 505 | */ 506 | FacileMenuItem *FacileMenuItem::breaker() 507 | { 508 | if (parent_menu_item_in_if) 509 | return parent_menu_item_in_if; 510 | return this; // 应该不会吧…… 511 | } 512 | 513 | /** 514 | * 如果switcher的caser没有满足 515 | */ 516 | FacileMenuItem *FacileMenuItem::defaulter() 517 | { 518 | if (switch_matched) // 已经有 caser 匹配了 519 | return createTempItem(); // 返回无效临时实例 520 | return this; // 能用,返回自己 521 | } 522 | 523 | /** 524 | * 返回自己的子菜单对象 525 | */ 526 | FacileMenu *FacileMenuItem::subMenu() 527 | { 528 | return sub_menu; 529 | } 530 | 531 | void FacileMenuItem::paintEvent(QPaintEvent *event) 532 | { 533 | InteractiveButtonBase::paintEvent(event); 534 | 535 | int right = width() - icon_text_size - 8; 536 | 537 | QPainter painter(this); 538 | if (isSubMenu()) 539 | { 540 | // 画右边箭头的图标 541 | QRect rect(right, fore_paddings.top, icon_text_size, icon_text_size); 542 | painter.drawPixmap(rect, QPixmap(":/icons/sub_menu_arrow")); 543 | } 544 | 545 | if (!shortcut_tip.isEmpty()) 546 | { 547 | // 画右边的文字 548 | QFontMetrics fm(this->font()); 549 | int width = fm.horizontalAdvance(shortcut_tip); 550 | painter.save(); 551 | auto c = painter.pen().color(); 552 | c.setAlpha(c.alpha() / 2); 553 | painter.setPen(c); 554 | painter.drawText(QRect(right-width, fore_paddings.top, width, height()-fore_paddings.top-fore_paddings.bottom), 555 | Qt::AlignRight, shortcut_tip); 556 | painter.restore(); 557 | } 558 | } 559 | 560 | void FacileMenuItem::drawIconBeforeText(QPainter &painter, QRect icon_rect) 561 | { 562 | // 选中 563 | if (checkable) 564 | { 565 | QPainterPath path; 566 | QRect expand_rect = icon_rect; 567 | expand_rect.adjust(-2, -2, 2, 2); 568 | path.addRoundedRect(expand_rect, 3, 3); 569 | if (InteractiveButtonBase::icon.isNull()) 570 | { 571 | // 绘制√ 572 | if (isChecked()) 573 | painter.drawText(icon_rect, "√"); 574 | } 575 | else // 有图标,使用 576 | { 577 | if (getState()) 578 | { 579 | // 绘制选中样式: 圆角矩形 580 | painter.fillPath(path, press_bg); 581 | } 582 | else 583 | { 584 | // 绘制未选中样式:空白边框 585 | painter.save(); 586 | painter.setPen(QPen(press_bg, 1)); 587 | painter.drawPath(path); 588 | painter.restore(); 589 | } 590 | } 591 | } 592 | 593 | InteractiveButtonBase::drawIconBeforeText(painter, icon_rect); 594 | } 595 | 596 | FacileMenuItem *FacileMenuItem::createTempItem(bool thisIsParent) 597 | { 598 | auto useless = new FacileMenuItem(QIcon(), "", this); 599 | useless->parent_menu_item_in_if = thisIsParent ? this : nullptr; 600 | useless->hide(); 601 | useless->setEnabled(false); 602 | useless->setMinimumSize(0, 0); 603 | useless->setFixedSize(0, 0); 604 | useless->move(-999, -999); 605 | return useless; 606 | } 607 | -------------------------------------------------------------------------------- /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 FuncItemType; 12 | typedef std::function const FuncItemIntType; 13 | 14 | class FacileMenuItem : public InteractiveButtonBase 15 | { 16 | public: 17 | FacileMenuItem(QWidget* parent = nullptr); 18 | FacileMenuItem(QString t, QWidget* parent = nullptr); 19 | FacileMenuItem(QIcon i, QWidget* parent = nullptr); 20 | FacileMenuItem(QIcon i, QString t, QWidget* parent = nullptr); 21 | FacileMenuItem(QPixmap p, QString t, QWidget* parent = nullptr); 22 | 23 | FacileMenuItem* setEnabled(bool e); 24 | FacileMenuItem* setCheckable(bool c); 25 | bool isCheckable() const; 26 | FacileMenuItem* setChecked(bool c); 27 | bool isChecked(); 28 | FacileMenuItem* setKey(Qt::Key key); 29 | bool isKey(Qt::Key key) const; 30 | FacileMenuItem* setSubMenu(FacileMenu* menu); 31 | bool isSubMenu() const; 32 | bool isLinger() const; 33 | FacileMenuItem* setData(QVariant data); 34 | QVariant getData(); 35 | 36 | FacileMenuItem* tip(QString sc); 37 | FacileMenuItem* tip(bool exp, QString sc); 38 | FacileMenuItem* tooltip(QString tt); 39 | FacileMenuItem* tooltip(bool exp, QString tt); 40 | FacileMenuItem* triggered(FuncType func); 41 | FacileMenuItem* triggered(bool exp, FuncType func); 42 | FacileMenuItem* disable(bool exp = true); // 满足情况下触发,不满足不变,下同 43 | FacileMenuItem* enable(bool exp = true); 44 | FacileMenuItem* hide(bool exp = true); 45 | FacileMenuItem* visible(bool exp = true); 46 | FacileMenuItem* check(bool exp = true); 47 | FacileMenuItem* uncheck(bool exp = true); 48 | FacileMenuItem* toggle(bool exp = true); 49 | FacileMenuItem* autoToggle(); 50 | FacileMenuItem* text(bool exp, QString str); 51 | FacileMenuItem* text(bool exp, QString tru, QString fal); 52 | FacileMenuItem* fgColor(QColor color); 53 | FacileMenuItem* fgColor(bool exp, QColor color); 54 | FacileMenuItem* bgColor(QColor color); 55 | FacileMenuItem* bgColor(bool exp, QColor color); 56 | FacileMenuItem* prefix(bool exp, QString pfix); 57 | FacileMenuItem* suffix(bool exp, QString sfix, bool inLeftParenthesis = true); 58 | FacileMenuItem* prefix(QString pfix); 59 | FacileMenuItem* suffix(QString sfix, bool inLeftParenthesis = true); 60 | FacileMenuItem* icon(bool exp, QIcon icon); 61 | FacileMenuItem* borderR(int radius = 3, QColor co = Qt::transparent); 62 | FacileMenuItem* linger(); 63 | FacileMenuItem* bind(bool &val); 64 | FacileMenuItem* longPress(FuncType func); 65 | 66 | FacileMenuItem* ifer(bool exp); 67 | FacileMenuItem* elifer(bool exp); 68 | FacileMenuItem* elser(); 69 | FacileMenuItem* exiter(bool exp = true); 70 | FacileMenuItem* ifer(bool exp, FuncItemType func, FuncItemType elseFunc = nullptr); 71 | FacileMenuItem* switcher(int value); 72 | FacileMenuItem* caser(int value, FuncType func); 73 | FacileMenuItem* caser(int value); 74 | FacileMenuItem* breaker(); 75 | FacileMenuItem* defaulter(); 76 | 77 | FacileMenu* subMenu(); 78 | 79 | protected: 80 | void paintEvent(QPaintEvent *event) override; 81 | void drawIconBeforeText(QPainter &painter, QRect icon_rect) override; 82 | 83 | FacileMenuItem* createTempItem(bool thisIsParent = true); 84 | 85 | private: 86 | Qt::Key key; 87 | bool checkable = false; 88 | bool trigger_linger = false; // 点击后是否保存菜单 89 | FacileMenu* sub_menu = nullptr; 90 | QString shortcut_tip = ""; // 快捷键提示 91 | FacileMenuItem* parent_menu_item_in_if = nullptr; // elser/caser专用 92 | int switch_value = 0; // switcher的值,用来和caser比较(不需要breaker……) 93 | bool switch_matched = true; 94 | QVariant data; 95 | }; 96 | 97 | #endif // FACILEMENUITEM_H 98 | -------------------------------------------------------------------------------- /interactive_buttons/interactivebuttonbase.h: -------------------------------------------------------------------------------- 1 | #ifndef INTERACTIVEBUTTONBASE_H 2 | #define INTERACTIVEBUTTONBASE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #define PI 3.1415926 19 | #define GOLDEN_RATIO 0.618 20 | 21 | #define DOUBLE_PRESS_INTERVAL 500 // /* 300 */松开和按下的间隔。相等为双击 22 | #define SINGLE_PRESS_INTERVAL 200 // /* 150 */按下时间超过这个数就是单击。相等为单击 23 | 24 | /** 25 | * Copyright (c) 2019 命燃芯乂 All rights reserved. 26 | × 27 | * 邮箱:wxy@iwxyi.com 28 | * QQ号:482582886 29 | * 时间:2020.09.09 30 | * 31 | * 说明:灵性的自定义按钮,简单又又去 32 | * 源码:https://github.com/MRXY001/Interactive-Windows-Buttons 33 | * 34 | * 本代码为本人编写方便自己使用,现在无私送给大家免费使用。 35 | * 程序版权归作者所有,只可使用不能出售,违反者本人有权追究责任。 36 | */ 37 | 38 | class InteractiveButtonBase : public QPushButton 39 | { 40 | Q_OBJECT 41 | Q_PROPERTY(bool self_enabled READ getSelfEnabled WRITE setSelfEnabled) // 是否启用自定义的按钮(true) 42 | Q_PROPERTY(bool parent_enabled READ getParentEnabled WRITE setParentEnabled) // 是否启用父类按钮(false) 43 | Q_PROPERTY(bool fore_enabled READ getForeEnabled WRITE setForeEnabled) // 是否绘制自定义按钮前景色(true) 44 | Q_PROPERTY(QString text READ getText WRITE setText) // 前景文字 45 | Q_PROPERTY(QString icon_path READ getIconPath WRITE setIconPath) // 前景图标 46 | Q_PROPERTY(QString pixmap_path READ getPixmapPath WRITE setPixmapPath) // 前景图标 47 | Q_PROPERTY(QColor icon_color READ getIconColor WRITE setIconColor) // 前景图标帅色 48 | Q_PROPERTY(QColor text_color READ getTextColor WRITE setTextColor) // 前景文字颜色 49 | Q_PROPERTY(QColor background_color READ getNormalColor WRITE setNormalColor) // 背景颜色 50 | Q_PROPERTY(QColor border_color READ getBorderColor WRITE setBorderColor) // 边界颜色 51 | Q_PROPERTY(QColor hover_color READ getHoverColor WRITE setHoverColor) // 鼠标悬浮背景颜色 52 | Q_PROPERTY(QColor press_color READ getPressColor WRITE setPressColor) // 鼠标按下背景颜色 53 | Q_PROPERTY(int hover_duration READ getHoverAniDuration WRITE setHoverAniDuration) // 鼠标悬浮动画周期 54 | Q_PROPERTY(int press_duration READ getPressAniDuration WRITE setPressAniDuration) // 鼠标按下动画周期 55 | Q_PROPERTY(int click_duration READ getClickAniDuration WRITE setClickAniDuration) // 鼠标点击动画周期 56 | Q_PROPERTY(double icon_padding_proper READ getIconPaddingProper WRITE setIconPaddingProper) // 图标四边空白处大小比例 57 | Q_PROPERTY(int radius READ getRadius WRITE setRadius) // 边框圆角半径 58 | Q_PROPERTY(int border_width READ getBorderWidth WRITE setBorderWidth) // 边框线条粗细 59 | Q_PROPERTY(bool fixed_fore_pos READ getFixedTextPos WRITE setFixedTextPos) // 是否固定前景位置(false) 60 | Q_PROPERTY(bool text_dynamic_size READ getTextDynamicSize WRITE setTextDynamicSize) // 修改字体大小时调整按钮最小尺寸(false) 61 | Q_PROPERTY(bool leave_after_clicked READ getLeaveAfterClick WRITE setLeaveAfterClick) // 鼠标单击松开后取消悬浮效果(针对菜单、弹窗) 62 | Q_PROPERTY(bool show_animation READ getShowAni WRITE setShowAni) // 是否启用出现动画(鼠标移开则消失)(false) 63 | Q_PROPERTY(bool water_animation READ getWaterRipple WRITE setWaterRipple) // 是否启用点击水波纹动画(否则使用渐变)(true) 64 | Q_PROPERTY(int font_size READ getFontSizeT WRITE setFontSizeT) // 动:按钮字体动画效果(自动,不应该设置) 65 | public: 66 | InteractiveButtonBase(QWidget *parent = nullptr); 67 | InteractiveButtonBase(QString text, QWidget *parent = nullptr); 68 | InteractiveButtonBase(QIcon icon, QWidget *parent = nullptr); 69 | InteractiveButtonBase(QPixmap pixmap, QWidget *parent = nullptr); 70 | InteractiveButtonBase(QIcon icon, QString text, QWidget *parent = nullptr); 71 | InteractiveButtonBase(QPixmap pixmap, QString text, QWidget *parent = nullptr); 72 | 73 | /** 74 | * 前景实体 75 | */ 76 | enum PaintModel 77 | { 78 | None, // 无前景,仅使用背景 79 | Text, // 纯文字(替代父类) 80 | Icon, // 纯图标 81 | PixmapMask, // 可变色图标(通过pixmap+遮罩实现),锯齿化明显 82 | IconText, // 图标+文字(强制左对齐) 83 | PixmapText // 变色图标+文字(强制左对齐) 84 | }; 85 | 86 | /** 87 | * 前景额外的图标(可以多个) 88 | * 可能是角标(比如展开箭头) 89 | * 可能时前缀(图例) 90 | */ 91 | struct PaintAddin 92 | { 93 | PaintAddin() : enable(false) {} 94 | PaintAddin(QPixmap p, Qt::Alignment a, QSize s) : enable(true), pixmap(p), align(a), size(s) {} 95 | bool enable; // 是否启用 96 | QPixmap pixmap; // 可变色图标 97 | Qt::Alignment align; // 对齐方式 98 | QSize size; // 固定大小 99 | }; 100 | 101 | /** 102 | * 鼠标松开时抖动动画 103 | * 松开的时候计算每一次抖动距离+时间,放入队列中 104 | * 定时调整抖动的队列实体索引 105 | */ 106 | struct Jitter 107 | { 108 | Jitter(QPoint p, qint64 t) : point(p), timestamp(t) {} 109 | QPoint point; // 要运动到的目标坐标 110 | qint64 timestamp; // 运动到目标坐标应该的时间戳,结束后删除本次抖动路径对象 111 | }; 112 | 113 | /** 114 | * 鼠标按下/弹起水波纹动画 115 | * 鼠标按下时动画速度慢(压住),松开后动画速度骤然加快 116 | * 同样用队列记录所有的水波纹动画实体 117 | */ 118 | struct Water 119 | { 120 | Water(QPoint p, qint64 t) : point(p), progress(0), press_timestamp(t), 121 | release_timestamp(0), finish_timestamp(0), finished(false) {} 122 | QPoint point; 123 | int progress; // 水波纹进度100%(已弃用,当前使用时间戳) 124 | qint64 press_timestamp; // 鼠标按下时间戳 125 | qint64 release_timestamp; // 鼠标松开时间戳。与按下时间戳、现行时间戳一起成为水波纹进度计算参数 126 | qint64 finish_timestamp; // 结束时间戳。与当前时间戳相减则为渐变消失经过的时间戳 127 | bool finished; // 是否结束。结束后改为渐变消失 128 | }; 129 | 130 | /** 131 | * 四周边界的padding 132 | * 调整按钮大小时:宽度+左右、高度+上下 133 | */ 134 | struct EdgeVal 135 | { 136 | EdgeVal() {} 137 | EdgeVal(int l, int t, int r, int b) : left(l), top(t), right(r), bottom(b) {} 138 | int left, top, right, bottom; // 四个边界的空白距离 139 | }; 140 | 141 | enum NolinearType 142 | { 143 | Linear, 144 | SlowFaster, 145 | FastSlower, 146 | SlowFastSlower, 147 | SpringBack20, 148 | SpringBack50 149 | }; 150 | 151 | virtual void setText(QString text); 152 | virtual void setIconPath(QString path); 153 | virtual void setIcon(QIcon icon); 154 | virtual void setPixmapPath(QString path); 155 | virtual void setPixmap(QPixmap pixmap); 156 | virtual void setPaintAddin(QPixmap pixmap, Qt::Alignment align = Qt::AlignRight, QSize size = QSize(0, 0)); 157 | 158 | void setSelfEnabled(bool e = true); 159 | void setParentEnabled(bool e = false); 160 | void setForeEnabled(bool e = true); 161 | 162 | void setHoverAniDuration(int d); 163 | void setPressAniDuration(int d); 164 | void setClickAniDuration(int d); 165 | void setWaterAniDuration(int press, int release, int finish); 166 | void setWaterRipple(bool enable = true); 167 | void setJitterAni(bool enable = true); 168 | void setUnifyGeomerey(bool enable = true); 169 | void setBgColor(QColor bg); 170 | void setBgColor(QColor hover, QColor press); 171 | void setNormalColor(QColor color); 172 | void setBorderColor(QColor color); 173 | void setHoverColor(QColor color); 174 | void setPressColor(QColor color); 175 | void setIconColor(QColor color = QColor(0, 0, 0)); 176 | void setTextColor(QColor color = QColor(0, 0, 0)); 177 | void setFocusBg(QColor color); 178 | void setFocusBorder(QColor color); 179 | void setFontSize(int f); 180 | void setHover(); 181 | void setAlign(Qt::Alignment a); 182 | void setRadius(int r); 183 | void setRadius(int rx, int ry); 184 | void setBorderWidth(int x); 185 | void setDisabled(bool dis = true); 186 | void setPaddings(int l, int r, int t, int b); 187 | void setPaddings(int h, int v); 188 | void setPaddings(int x); 189 | void setIconPaddingProper(double x); 190 | void setFixedForePos(bool f = true); 191 | void setFixedForeSize(bool f = true, int addin = 0); 192 | void setSquareSize(); 193 | void setTextDynamicSize(bool d = true); 194 | void setLeaveAfterClick(bool l = true); 195 | void setDoubleClicked(bool e = true); 196 | void setAutoTextColor(bool a = true); 197 | void setPretendFocus(bool f = true); 198 | void setBlockHover(bool b = true); 199 | 200 | void setShowAni(bool enable = true); 201 | void showForeground(); 202 | void showForeground2(QPoint point = QPoint(0, 0)); 203 | void hideForeground(); 204 | void delayShowed(int time, QPoint point = QPoint(0, 0)); 205 | 206 | QString getText(); 207 | void setMenu(QMenu *menu); 208 | void setState(bool s = true); 209 | bool getState(); 210 | virtual void simulateStatePress(bool s = true, bool a = false); 211 | bool isHovering() { return hovering; } 212 | bool isPressing() { return pressing; } 213 | void simulateHover(); 214 | void discardHoverPress(bool force = false); 215 | 216 | bool getSelfEnabled() { return self_enabled; } 217 | bool getParentEnabled() { return parent_enabled; } 218 | bool getForeEnabled() { return fore_enabled; } 219 | QColor getIconColor() { return icon_color; } 220 | QColor getTextColor() { return text_color; } 221 | QColor getNormalColor() { return normal_bg; } 222 | QColor getBorderColor() { return border_bg; } 223 | QColor getHoverColor() { return hover_bg; } 224 | QColor getPressColor() { return press_bg; } 225 | QString getIconPath() { return ""; } 226 | QString getPixmapPath() { return ""; } 227 | int getHoverAniDuration() { return hover_bg_duration; } 228 | int getPressAniDuration() { return press_bg_duration; } 229 | int getClickAniDuration() { return click_ani_duration; } 230 | double getIconPaddingProper() { return icon_padding_proper; } 231 | int getRadius() { return qMax(radius_x, radius_y); } 232 | int getBorderWidth() { return border_width; } 233 | bool getFixedTextPos() { return fixed_fore_pos; } 234 | bool getTextDynamicSize() { return text_dynamic_size; } 235 | bool getLeaveAfterClick() { return leave_after_clicked; } 236 | bool getShowAni() { return show_animation; } 237 | bool getWaterRipple() { return water_animation; } 238 | 239 | #if QT_DEPRECATED_SINCE(5, 11) 240 | QT_DEPRECATED_X("Use InteractiveButtonBase::setFixedForePos(bool fixed = true)") 241 | void setFixedTextPos(bool f = true); 242 | #endif 243 | 244 | protected: 245 | void enterEvent(QEvent *event) override; 246 | void leaveEvent(QEvent *event) override; 247 | void mousePressEvent(QMouseEvent *event) override; 248 | void mouseReleaseEvent(QMouseEvent *event) override; 249 | void mouseMoveEvent(QMouseEvent *event) override; 250 | void resizeEvent(QResizeEvent *event) override; 251 | void focusInEvent(QFocusEvent *event) override; 252 | void focusOutEvent(QFocusEvent *event) override; 253 | void changeEvent(QEvent *event) override; 254 | void paintEvent(QPaintEvent *event) override; 255 | 256 | virtual bool inArea(QPoint point); 257 | virtual QPainterPath getBgPainterPath(); 258 | virtual QPainterPath getWaterPainterPath(Water water); 259 | virtual void drawIconBeforeText(QPainter &painter, QRect icon_rect); 260 | 261 | QRect getUnifiedGeometry(); 262 | void updateUnifiedGeometry(); 263 | void paintWaterRipple(QPainter &painter); 264 | void setJitter(); 265 | 266 | int getFontSizeT(); 267 | void setFontSizeT(int f); 268 | 269 | int max(int a, int b) const; 270 | int min(int a, int b) const; 271 | int quick_sqrt(long X) const; 272 | qint64 getTimestamp() const; 273 | bool isLightColor(QColor color); 274 | int getSpringBackProgress(int x, int max); 275 | QColor getOpacityColor(QColor color, double level = 0.5); 276 | QPixmap getMaskPixmap(QPixmap p, QColor c); 277 | 278 | double getNolinearProg(int p, NolinearType type); 279 | QIcon::Mode getIconMode(); 280 | 281 | signals: 282 | void showAniFinished(); 283 | void hideAniFinished(); 284 | void pressAppearAniFinished(); 285 | void pressDisappearAniFinished(); 286 | void jitterAniFinished(); 287 | void doubleClicked(); 288 | void rightClicked(); 289 | void signalFocusIn(); 290 | void signalFocusOut(); 291 | 292 | void signalMouseEnter(); 293 | void signalMouseEnterLater(); // 进入后延迟信号(以渐变动画完成为准,相当于可手动设置) 294 | void signalMouseLeave(); 295 | void signalMouseLeaveLater(); // 离开后延迟的信号(直至渐变动画完成(要是划过一下子离开,这个也会变快)) 296 | void signalMousePress(QMouseEvent* event); 297 | void signalMousePressLater(QMouseEvent* event); 298 | void signalMouseRelease(QMouseEvent* event); 299 | void signalMouseReleaseLater(QMouseEvent* event); 300 | 301 | public slots: 302 | virtual void anchorTimeOut(); 303 | virtual void slotClicked(); 304 | void slotCloseState(); 305 | 306 | protected: 307 | PaintModel model; 308 | QIcon icon; 309 | QString text; 310 | QPixmap pixmap; 311 | PaintAddin paint_addin; 312 | EdgeVal fore_paddings; 313 | 314 | protected: 315 | // 总体开关 316 | bool self_enabled, parent_enabled, fore_enabled; // 是否启用子类、启动父类、绘制子类前景 317 | 318 | // 出现前景的动画 319 | bool show_animation, show_foreground; 320 | bool show_ani_appearing, show_ani_disappearing; 321 | int show_duration; 322 | qint64 show_timestamp, hide_timestamp; 323 | int show_ani_progress; 324 | QPoint show_ani_point; 325 | QRect paint_rect; 326 | 327 | // 鼠标开始悬浮、按下、松开、离开的坐标和时间戳 328 | // 鼠标锚点、目标锚点、当前锚点的坐标;当前XY的偏移量 329 | QPoint enter_pos, press_pos, release_pos, mouse_pos, anchor_pos /*目标锚点渐渐靠近鼠标*/; 330 | QPoint offset_pos /*当前偏移量*/, effect_pos, release_offset; // 相对中心、相对左上角、弹起时的平方根偏移 331 | bool hovering, pressing; // 是否悬浮和按下的状态机 332 | qint64 hover_timestamp, leave_timestamp, press_timestamp, release_timestamp; // 各种事件的时间戳 333 | int hover_bg_duration, press_bg_duration, click_ani_duration; // 各种动画时长 334 | 335 | // 定时刷新界面(保证动画持续) 336 | QTimer *anchor_timer; 337 | int move_speed; 338 | 339 | // 背景与前景 340 | QColor icon_color, text_color; // 前景颜色 341 | QColor normal_bg, hover_bg, press_bg, border_bg; // 各种背景颜色 342 | QColor focus_bg, focus_border; // 有焦点的颜色 343 | int hover_speed, press_start, press_speed; // 颜色渐变速度 344 | int hover_progress, press_progress; // 颜色渐变进度 345 | double icon_padding_proper; // 图标的大小比例 346 | int icon_text_padding, icon_text_size; // 图标+文字模式共存时,两者间隔、图标大小 347 | int border_width; 348 | int radius_x, radius_y; 349 | int font_size; 350 | bool fixed_fore_pos; // 鼠标进入时是否固定文字位置 351 | bool fixed_fore_size; // 鼠标进入/点击时是否固定前景大小 352 | bool text_dynamic_size; // 设置字体时自动调整最小宽高 353 | bool auto_text_color; // 动画时是否自动调整文字颜色 354 | bool focusing; // 是否获得了焦点 355 | 356 | // 鼠标单击动画 357 | bool click_ani_appearing, click_ani_disappearing; // 是否正在按下的动画效果中 358 | int click_ani_progress; // 按下的进度(使用时间差计算) 359 | QMouseEvent *mouse_press_event, *mouse_release_event; 360 | 361 | // 统一绘制图标的区域(从整个按钮变为中心三分之二,并且根据偏移计算) 362 | bool unified_geometry; // 上面用不到的话,这个也用不到…… 363 | int _l, _t, _w, _h; 364 | 365 | // 鼠标拖拽弹起来回抖动效果 366 | bool jitter_animation; // 是否开启鼠标松开时的抖动效果 367 | double elastic_coefficient; // 弹性系数 368 | QList jitters; 369 | int jitter_duration; // 抖动一次,多次效果叠加 370 | 371 | // 鼠标按下水波纹动画效果 372 | bool water_animation; // 是否开启水波纹动画 373 | QList waters; 374 | int water_press_duration, water_release_duration, water_finish_duration; 375 | int water_radius; 376 | 377 | // 其他效果 378 | Qt::Alignment align; // 文字/图标对其方向 379 | bool _state; // 一个记录状态的变量,比如是否持续 380 | bool leave_after_clicked; // 鼠标单击松开后取消悬浮效果(针对菜单、弹窗),按钮必定失去焦点 381 | bool _block_hover; // 如果有出现动画,临时屏蔽hovering效果 382 | 383 | // 双击 384 | bool double_clicked; // 开启双击 385 | QTimer *double_timer; // 双击时钟 386 | bool double_prevent; // 双击阻止单击release的flag 387 | }; 388 | 389 | #endif // INTERACTIVEBUTTONBASE_H 390 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "orderplayerwindow.h" 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QApplication a(argc, argv); 8 | OrderPlayerWindow w; 9 | w.show(); 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /order_player/clickslider.h: -------------------------------------------------------------------------------- 1 | #ifndef CLICKSLIDER_H 2 | #define CLICKSLIDER_H 3 | 4 | #include 5 | #include 6 | 7 | class ClickSlider : public QSlider 8 | { 9 | Q_OBJECT 10 | public: 11 | ClickSlider(QWidget* parent = nullptr) : QSlider(parent) 12 | { 13 | } 14 | 15 | signals: 16 | void signalClickMove(int pos); // 需要手动连接 信号槽 17 | 18 | protected: 19 | void mousePressEvent(QMouseEvent *e) override 20 | { 21 | // 百分比 22 | double p = (double)e->pos().x() / (double)width(); 23 | int val = p*(maximum()-minimum())+minimum(); 24 | setValue(val); 25 | QSlider::mousePressEvent(e); 26 | 27 | emit signalClickMove(val); 28 | } 29 | }; 30 | 31 | #endif // CLICKSLIDER_H 32 | -------------------------------------------------------------------------------- /order_player/desktoplyricwidget.cpp: -------------------------------------------------------------------------------- 1 | #include "desktoplyricwidget.h" 2 | 3 | DesktopLyricWidget::DesktopLyricWidget(QSettings& settings, QWidget *parent) : QWidget(parent), 4 | settings(settings) 5 | { 6 | this->setWindowTitle("桌面歌词"); 7 | this->setMinimumSize(45, 25); //设置最小尺寸 8 | this->setMaximumSize(800, 100); 9 | if ((jiWindow = settings.value("music/desktopLyricTrans", false).toBool())) 10 | { 11 | this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); //设置为无边框置顶窗口 12 | } 13 | else 14 | { 15 | this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::Tool); //设置为无边框置顶窗口 16 | this->setAttribute(Qt::WA_TranslucentBackground, true); // 设置窗口透明 17 | } 18 | this->setContextMenuPolicy(Qt::CustomContextMenu); 19 | this->setMouseTracking(true); 20 | 21 | QFontMetrics fm(this->font()); 22 | fontHeight = fm.height(); 23 | lineSpacing = fm.lineSpacing(); 24 | 25 | connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(showMenu())); 26 | 27 | lineMode = static_cast(settings.value("music/lineMode", lineMode).toInt()); 28 | alignMode = static_cast(settings.value("music/alignMode", alignMode).toInt()); 29 | playingColor = qvariant_cast(settings.value("music/playingColor", playingColor)); 30 | waitingColor = qvariant_cast(settings.value("music/waitingColor", waitingColor)); 31 | bgColor = qvariant_cast(settings.value("music/bgColor", bgColor)); 32 | pointSize = settings.value("music/desktopLyricPointSize", pointSize).toInt(); 33 | } 34 | 35 | /** 36 | * 歌词文本设置成歌词流 37 | * 在这里解析格式 38 | */ 39 | void DesktopLyricWidget::setLyric(QString text) 40 | { 41 | // 检测是不是全是毫秒还是10毫秒的 42 | int ms10x = 10; 43 | QRegularExpression re10("\\[\\d{2}:\\d{2}(?:\\.([1-9]\\d{2}))?\\]"); 44 | QRegularExpressionMatch match10; 45 | if (text.lastIndexOf(re10, -1, &match10) != -1) 46 | { 47 | int val = match10.captured(1).toInt(); 48 | if (val > 0) // 存在不为0的三位数 49 | { 50 | ms10x = 1; 51 | } 52 | } 53 | 54 | // 遍历每一行 55 | QStringList sl = text.split("\n", QString::SkipEmptyParts); 56 | LyricBean prevLyric(false); 57 | qint64 currentTime = 0; 58 | lyricStream.clear(); 59 | foreach (QString line, sl) 60 | { 61 | QRegularExpression re("^\\[(\\d{2}):(\\d{2})(?:\\.(\\d{2,3}))?\\](\\[(\\d{2}):(\\d{2})\\.(\\d{2,3})\\])?(.*)$"); 62 | QRegularExpressionMatch match; 63 | if (line.indexOf(re, 0, &match) == -1) // 不是时间格式的,可能是其他标签 64 | { 65 | if (line.indexOf(QRegularExpression("^\\s*\\[.+:.*\\]\\s*$")) > -1) // 是标签,无视掉 66 | continue; 67 | else if (line.indexOf(QRegularExpression("\\[00:00:00\\](.+)"), 0, &match) > -1) 68 | { 69 | LyricBean lyric; 70 | lyric.start = 0; 71 | lyric.end = 0; 72 | lyric.text = match.captured(1); 73 | lyricStream.append(lyric); 74 | continue; 75 | } 76 | 77 | LyricBean lyric; 78 | lyric.start = currentTime; 79 | lyric.end = 0; 80 | lyric.text = line; 81 | lyricStream.append(lyric); 82 | continue; 83 | } 84 | QStringList caps = match.capturedTexts(); 85 | LyricBean lyric; 86 | int minute = caps.at(1).toInt(); 87 | int second = caps.at(2).toInt(); 88 | int ms10 = caps.at(3).toInt(); 89 | lyric.start = minute * 60000 + second*1000 + ms10 * ms10x; 90 | if (!caps.at(4).isEmpty()) // 有终止时间 91 | { 92 | int minute = caps.at(5).toInt(); 93 | int second = caps.at(6).toInt(); 94 | int ms10 = caps.at(7).toInt(); 95 | lyric.end = minute * 60000 + second*1000 + ms10 * ms10x; 96 | } 97 | lyric.text = caps.at(8); 98 | lyricStream.append(lyric); 99 | currentTime = lyric.start; 100 | } 101 | 102 | currentRow = 0; 103 | update(); 104 | } 105 | 106 | void DesktopLyricWidget::setColors(QColor p, QColor w) 107 | { 108 | playingColor = p; 109 | waitingColor = w; 110 | update(); 111 | } 112 | 113 | void DesktopLyricWidget::showEvent(QShowEvent *event) 114 | { 115 | restoreGeometry(settings.value("music/desktopLyricGeometry").toByteArray()); 116 | } 117 | 118 | void DesktopLyricWidget::hideEvent(QHideEvent *event) 119 | { 120 | settings.setValue("music/desktopLyricGeometry", this->saveGeometry()); 121 | } 122 | 123 | bool DesktopLyricWidget::nativeEvent(const QByteArray &eventType, void *message, long *result) 124 | { 125 | Q_UNUSED(eventType) 126 | MSG* msg = static_cast(message); 127 | switch(msg->message) 128 | { 129 | case WM_NCHITTEST: 130 | const auto ratio = devicePixelRatioF(); // 解决4K下的问题 131 | int xPos = static_cast(GET_X_LPARAM(msg->lParam) / ratio - this->frameGeometry().x()); 132 | int yPos = static_cast(GET_Y_LPARAM(msg->lParam) / ratio - this->frameGeometry().y()); 133 | if(xPos < boundaryWidth &&yPos=width()-boundaryWidth&&yPos=height()-boundaryWidth) //左下角 138 | *result = HTBOTTOMLEFT; 139 | else if(xPos>=width()-boundaryWidth&&yPos>=height()-boundaryWidth)//右下角 140 | *result = HTBOTTOMRIGHT; 141 | else if(xPos < boundaryWidth) //左边 142 | *result = HTLEFT; 143 | else if(xPos>=width()-boundaryWidth) //右边 144 | *result = HTRIGHT; 145 | /*else if(yPos=height()-boundaryWidth) //下边 148 | *result = HTBOTTOM; 149 | else //其他部分不做处理,返回false,留给其他事件处理器处理 150 | return false; 151 | return true; 152 | } 153 | return false; //此处返回false,留给其他事件处理器处理 154 | } 155 | 156 | void DesktopLyricWidget::enterEvent(QEvent *event) 157 | { 158 | hovering = true; 159 | update(); 160 | } 161 | 162 | void DesktopLyricWidget::leaveEvent(QEvent *event) 163 | { 164 | if (!this->geometry().contains(QCursor::pos())) 165 | { 166 | hovering = false; 167 | } 168 | else 169 | { 170 | QTimer::singleShot(300, [=]{ 171 | if (!this->geometry().contains(QCursor::pos())) 172 | { 173 | hovering = false; 174 | update(); 175 | } 176 | }); 177 | } 178 | update(); 179 | } 180 | 181 | void DesktopLyricWidget::mousePressEvent(QMouseEvent *e) 182 | { 183 | if(e->button()==Qt::LeftButton) 184 | { 185 | pressPos = e->pos(); 186 | } 187 | QWidget::mousePressEvent(e); 188 | } 189 | 190 | void DesktopLyricWidget::mouseMoveEvent(QMouseEvent *e) 191 | { 192 | hovering = true; 193 | if(e->buttons()&Qt::LeftButton) 194 | { 195 | move(QCursor::pos() - pressPos); 196 | } 197 | QWidget::mouseMoveEvent(e); 198 | } 199 | 200 | void DesktopLyricWidget::resizeEvent(QResizeEvent *) 201 | { 202 | 203 | } 204 | 205 | void DesktopLyricWidget::paintEvent(QPaintEvent *) 206 | { 207 | QPainter painter(this); 208 | 209 | // 绘制背景 210 | if (jiWindow) 211 | { 212 | painter.fillRect(this->rect(), bgColor); 213 | } 214 | if (hovering) 215 | { 216 | painter.setRenderHint(QPainter::Antialiasing, true); 217 | QPainterPath path; 218 | path.addRoundedRect(this->rect(), 5, 5); 219 | painter.fillPath(path, QColor(64, 64, 64, 32)); 220 | } 221 | 222 | // 绘制桌面歌词 223 | QFont font; 224 | font.setPointSize(pointSize); 225 | font.setBold(true); 226 | painter.setFont(font); 227 | 228 | // 文字区域 229 | QRect rect = this->rect(); 230 | rect.setLeft(rect.left() + boundaryWidth); 231 | rect.setTop(rect.top() + boundaryWidth); 232 | rect.setRight(rect.right() - boundaryWidth); 233 | rect.setBottom(rect.bottom() - boundaryWidth); 234 | 235 | if (currentRow > -1 && currentRow < lyricStream.size()) 236 | { 237 | bool cross = lineMode != SingleLine && currentRow % 2; 238 | 239 | // 绘制当前句 240 | if (currentRow + (cross ? 1 : 0) < lyricStream.size()) 241 | { 242 | painter.setPen(cross ? waitingColor : playingColor); 243 | QFlags align; 244 | if (lineMode == SuitableLine || lineMode == DoubleLine) 245 | align = Qt::AlignTop; 246 | else if (lineMode == SingleLine) 247 | align = Qt::AlignVCenter; 248 | if (alignMode == AlignMid) 249 | align |= Qt::AlignHCenter; 250 | else if (alignMode == AlignRight) 251 | align |= Qt::AlignRight; 252 | else 253 | align |= Qt::AlignLeft; 254 | painter.drawText(rect, align, lyricStream.at(currentRow + (cross ? 1 : 0)).text); 255 | } 256 | 257 | // 绘制下一句 258 | if (currentRow - (cross ? 1 : 0) < lyricStream.size()-1 && (lineMode == SuitableLine || lineMode == DoubleLine)) 259 | { 260 | painter.setPen(cross ? playingColor : waitingColor); 261 | QFlags align = Qt::AlignBottom; 262 | if (alignMode == AlignMid) 263 | align |= Qt::AlignHCenter; 264 | else if (alignMode == AlignLeft) 265 | align |= Qt::AlignLeft; 266 | else 267 | align |= Qt::AlignRight; 268 | 269 | painter.drawText(rect, align, lyricStream.at(currentRow + (cross ? 0 : 1)).text); 270 | } 271 | } 272 | } 273 | 274 | void DesktopLyricWidget::showMenu() 275 | { 276 | FacileMenu* menu = new FacileMenu(this); 277 | auto lineMenu = menu->addMenu("行数"); 278 | lineMenu->addOptions(QStringList{"自动", "单行", "双行"}, lineMode, [=](int index){ 279 | lineMode = static_cast(index); 280 | settings.setValue("music/lineMode", lineMode); 281 | update(); 282 | }); 283 | auto alignMenu = menu->addMenu("对齐"); 284 | alignMenu->addOptions(QStringList{"居中", "左右分离", "左对齐", "右对齐"}, alignMode, [=](int index){ 285 | alignMode = static_cast(index); 286 | settings.setValue("music/alignMode", alignMode); 287 | update(); 288 | }); 289 | menu->addAction("已播放颜色", [=]{ 290 | QColor c = QColorDialog::getColor(playingColor, this, "选择已播放颜色", QColorDialog::ShowAlphaChannel); 291 | if (!c.isValid()) 292 | return ; 293 | if (c != playingColor) 294 | { 295 | settings.setValue("music/playingColor", playingColor = c); 296 | update(); 297 | } 298 | })->fgColor(playingColor); 299 | menu->addAction("未播放颜色", [=]{ 300 | QColor c = QColorDialog::getColor(waitingColor, this, "选择未播放颜色", QColorDialog::ShowAlphaChannel); 301 | if (!c.isValid()) 302 | return ; 303 | if (c != waitingColor) 304 | { 305 | settings.setValue("music/waitingColor", waitingColor = c); 306 | update(); 307 | } 308 | })->fgColor(waitingColor); 309 | menu->addAction("背景颜色", [=]{ 310 | QColor c = QColorDialog::getColor(bgColor, this, "选择背景颜色", QColorDialog::ShowAlphaChannel); 311 | if (!c.isValid()) 312 | return ; 313 | if (c != bgColor) 314 | { 315 | settings.setValue("music/bgColor", bgColor = c); 316 | update(); 317 | } 318 | })->bgColor(bgColor)->hide(!jiWindow); 319 | auto fontMenu = menu->addMenu("字体大小"); 320 | QStringList sl; 321 | for (int i = 12; i < 30; i++) 322 | sl << QString::number(i); 323 | fontMenu->addOptions(sl, pointSize-12, [=](int index){ 324 | pointSize = index + 12; 325 | settings.setValue("music/desktopLyricPointSize", pointSize); 326 | update(); 327 | }); 328 | menu->split()->addAction("透明模式", [=]{ 329 | bool trans = settings.value("music/desktopLyricTrans", false).toBool(); 330 | settings.setValue("music/desktopLyricTrans", !trans); 331 | emit signalSwitchTrans(); 332 | })->ifer(!jiWindow)->check(); 333 | menu->addAction("隐藏", [=]{ 334 | this->hide(); 335 | emit signalhide(); 336 | }); 337 | menu->exec(QCursor::pos()); 338 | } 339 | 340 | void DesktopLyricWidget::setPosition(qint64 position) 341 | { 342 | if (!lyricStream.size()) 343 | return ; 344 | if (currentRow < 0 || currentRow >= lyricStream.size()) 345 | currentRow = 0; 346 | 347 | LyricBean lyric = lyricStream.at(currentRow); 348 | if (currentRow == lyricStream.size()-1 && lyric.start <= position) // 已经到末尾了 349 | return ; 350 | 351 | LyricBean nextLyric = currentRow == lyricStream.size()-1 ? LyricBean(false) : lyricStream.at(currentRow + 1); 352 | if (lyric.start <= position && nextLyric.start >= position) // 不需要改变 353 | return ; 354 | 355 | if (lyric.start > position) // 什么情况?从头强制重新开始! 356 | currentRow = 0; 357 | 358 | while (currentRow+1 < lyricStream.size() && lyricStream.at(currentRow+1).start < position) 359 | { 360 | currentRow++; 361 | } 362 | update(); 363 | } 364 | -------------------------------------------------------------------------------- /order_player/desktoplyricwidget.h: -------------------------------------------------------------------------------- 1 | #ifndef DESKTOPLYRICWIDGET_H 2 | #define DESKTOPLYRICWIDGET_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 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include "facilemenu.h" 34 | 35 | struct LyricBean 36 | { 37 | qint64 start = 0; 38 | qint64 end = 0; 39 | QString text; 40 | 41 | LyricBean(){} 42 | LyricBean(bool):start(-1),end(-1){} 43 | }; 44 | 45 | typedef QList LyricStream; 46 | 47 | class DesktopLyricWidget : public QWidget 48 | { 49 | Q_OBJECT 50 | public: 51 | explicit DesktopLyricWidget(QSettings &settings, QWidget *parent = nullptr); 52 | 53 | enum LineMode 54 | { 55 | SuitableLine, 56 | SingleLine, 57 | DoubleLine 58 | }; 59 | 60 | enum AlignMode 61 | { 62 | AlignMid, 63 | AlignWidth, 64 | AlignLeft, 65 | AlignRight, 66 | }; 67 | 68 | void setLyric(QString text); 69 | 70 | void setColors(QColor p, QColor w); 71 | 72 | protected: 73 | void showEvent(QShowEvent *event) override; 74 | void hideEvent(QHideEvent *event) override; 75 | bool nativeEvent(const QByteArray &eventType, void *message, long *result) override; 76 | void enterEvent(QEvent *event) override; 77 | void leaveEvent(QEvent *event) override; 78 | void mousePressEvent(QMouseEvent *e) override; 79 | void mouseMoveEvent(QMouseEvent *e) override; 80 | void resizeEvent(QResizeEvent *) override; 81 | void paintEvent(QPaintEvent *) override; 82 | 83 | signals: 84 | void signalhide(); 85 | void signalSwitchTrans(); 86 | 87 | public slots: 88 | void showMenu(); 89 | void setPosition(qint64 position); 90 | 91 | private: 92 | QSettings& settings; 93 | 94 | // 几何数值 95 | int fontHeight; 96 | int lineSpacing; 97 | int boundaryWidth = 8; 98 | int boundaryShowed = 2; 99 | QPoint pressPos; 100 | bool hovering = false; 101 | 102 | // 绘制设定 103 | LineMode lineMode = DoubleLine; // 单行还是双行 104 | AlignMode alignMode = AlignLeft; // 0左 1中 2右 3分散 105 | QColor playingColor = QColor(155, 80, 255); 106 | QColor waitingColor = QColor(255, 155, 155); 107 | QColor bgColor = QColor(0xf0, 0xf0, 0xf0); 108 | 109 | // 歌词显示 110 | LyricStream lyricStream; 111 | int currentRow = -1; // 当前播放或即将播放的歌词row(不一定有下一行) 112 | int pointSize = 20; 113 | 114 | bool jiWindow = false; 115 | }; 116 | 117 | #endif // DESKTOPLYRICWIDGET_H 118 | -------------------------------------------------------------------------------- /order_player/itemselectionlistview.h: -------------------------------------------------------------------------------- 1 | #ifndef ITEMSELECTIONLISTVIEW_H 2 | #define ITEMSELECTIONLISTVIEW_H 3 | 4 | #include 5 | 6 | class ItemSelectionListView : public QListView 7 | { 8 | Q_OBJECT 9 | public: 10 | ItemSelectionListView(QWidget* parent = nullptr) : QListView(parent) 11 | {} 12 | 13 | signals: 14 | void itemSelectionChanged(); 15 | 16 | protected: 17 | void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) override 18 | { 19 | emit itemSelectionChanged(); 20 | QListView::selectionChanged(selected, deselected); 21 | } 22 | 23 | }; 24 | 25 | #endif // ITEMSELECTIONLISTVIEW_H 26 | -------------------------------------------------------------------------------- /order_player/logindialog.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "logindialog.h" 3 | #include "ui_logindialog.h" 4 | 5 | LoginDialog::LoginDialog(QWidget *parent) : 6 | QDialog(parent), 7 | ui(new Ui::LoginDialog) 8 | { 9 | ui->setupUi(this); 10 | setAttribute(Qt::WA_DeleteOnClose, true); 11 | setWindowFlag(Qt::WindowContextHelpButtonHint, false); 12 | 13 | ui->stackedWidget->setCurrentIndex(0); 14 | } 15 | 16 | LoginDialog::~LoginDialog() 17 | { 18 | delete ui; 19 | } 20 | 21 | void LoginDialog::on_loginButton_clicked() 22 | { 23 | if (ui->neteaseRadio->isChecked()) 24 | { 25 | QString username = ui->usernameEdit->text().trimmed(); 26 | QString password = ui->passwordEdit->text().trimmed(); 27 | if (username.isEmpty() || password.isEmpty()) 28 | return ; 29 | loginNetease(username, password); 30 | } 31 | else if (ui->neteaseCookieRadio->isChecked()) 32 | { 33 | QString cookies = ui->cookieEdit->toPlainText(); 34 | if (cookies.isEmpty()) 35 | return ; 36 | cookieNetease(cookies); 37 | } 38 | else if (ui->qqmusicCookieRadio->isChecked()) 39 | { 40 | QString cookies = ui->cookieEdit->toPlainText(); 41 | if (cookies.isEmpty()) 42 | return ; 43 | cookieQQMusic(cookies); 44 | } 45 | } 46 | 47 | void LoginDialog::loginNetease(QString username, QString password) 48 | { 49 | password = password.toLocal8Bit().toPercentEncoding(); 50 | bool usePhone = (username.indexOf("@") == -1); 51 | QString url = NETEASE_SERVER + (usePhone 52 | ? "/login/cellphone?phone=" + username +"&password=" + password 53 | : "/login?email=" + username + "&password=" + password); 54 | 55 | QNetworkAccessManager* manager = new QNetworkAccessManager; 56 | QNetworkRequest* request = new QNetworkRequest(url); 57 | request->setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded; charset=UTF-8"); 58 | request->setHeader(QNetworkRequest::UserAgentHeader, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36"); 59 | connect(manager, &QNetworkAccessManager::finished, this, [=](QNetworkReply* reply){ 60 | QJsonParseError error; 61 | QByteArray ba = reply->readAll(); 62 | QJsonDocument document = QJsonDocument::fromJson(ba, &error); 63 | if (error.error != QJsonParseError::NoError) 64 | { 65 | qDebug() << error.errorString(); 66 | return ; 67 | } 68 | QJsonObject json = document.object(); 69 | int code = json.value("code").toInt(); 70 | if (code != 200) 71 | { 72 | QMessageBox::warning(this, "登录错误", "账号或密码错误,请重试\n错误码:" + QString::number(code)); 73 | return ; 74 | } 75 | 76 | if(reply->hasRawHeader("Set-Cookie")) 77 | { 78 | QByteArray cookie = reply->rawHeader("Set-Cookie"); 79 | qDebug() << "cookie:" << cookie; 80 | qDebug() << "data:" << ba; 81 | emit signalLogined(NeteaseCloudMusic, cookie); 82 | } 83 | 84 | manager->deleteLater(); 85 | delete request; 86 | reply->deleteLater(); 87 | this->close(); 88 | }); 89 | manager->get(*request); 90 | } 91 | 92 | void LoginDialog::cookieNetease(QString cookies) 93 | { 94 | emit signalLogined(NeteaseCloudMusic, cookies); 95 | this->close(); 96 | } 97 | 98 | void LoginDialog::cookieQQMusic(QString cookies) 99 | { 100 | emit signalLogined(QQMusic, cookies); 101 | this->close(); 102 | } 103 | 104 | void LoginDialog::on_neteaseRadio_clicked() 105 | { 106 | ui->stackedWidget->setCurrentIndex(0); 107 | } 108 | 109 | void LoginDialog::on_cookieHelpButton_clicked() 110 | { 111 | QString text = "目前只支持用户自己从 网易云音乐/QQ音乐 的网页版获取获取 Cookie。\n\n步骤:\n浏览器登录按 F12 打开开发者工具, 找到 Network\n随便进入网易云音乐/QQ音乐的网页, 复制右边 request headers 中的 Cookie"; 112 | QMessageBox::information(this, "获取Cookies", text); 113 | } 114 | 115 | void LoginDialog::on_neteaseCookieRadio_clicked() 116 | { 117 | ui->stackedWidget->setCurrentIndex(1); 118 | } 119 | 120 | void LoginDialog::on_qqmusicCookieRadio_clicked() 121 | { 122 | ui->stackedWidget->setCurrentIndex(1); 123 | } 124 | -------------------------------------------------------------------------------- /order_player/logindialog.h: -------------------------------------------------------------------------------- 1 | #ifndef LOGINDIALOG_H 2 | #define LOGINDIALOG_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "songbeans.h" 8 | 9 | namespace Ui { 10 | class LoginDialog; 11 | } 12 | 13 | #define NETEASE_SERVER QString("http://iwxyi.com:3000") 14 | #define QQMUSIC_SERVER QString("http://iwxyi.com:3200") 15 | 16 | class LoginDialog : public QDialog 17 | { 18 | Q_OBJECT 19 | public: 20 | explicit LoginDialog(QWidget *parent = nullptr); 21 | ~LoginDialog(); 22 | 23 | private slots: 24 | void on_loginButton_clicked(); 25 | 26 | void on_neteaseRadio_clicked(); 27 | 28 | void on_cookieHelpButton_clicked(); 29 | 30 | void on_neteaseCookieRadio_clicked(); 31 | 32 | void on_qqmusicCookieRadio_clicked(); 33 | 34 | private: 35 | void loginNetease(QString username, QString password); 36 | void cookieNetease(QString cookies); 37 | void cookieQQMusic(QString cookies); 38 | 39 | signals: 40 | void signalLogined(MusicSource source, QString cookies); 41 | 42 | private: 43 | Ui::LoginDialog *ui; 44 | }; 45 | 46 | #endif // LOGINDIALOG_H 47 | -------------------------------------------------------------------------------- /order_player/logindialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | LoginDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 382 10 | 209 11 | 12 | 13 | 14 | 网易云账号登录 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 网易云登录 23 | 24 | 25 | true 26 | 27 | 28 | 29 | 30 | 31 | 32 | 网易云Cookie 33 | 34 | 35 | 36 | 37 | 38 | 39 | true 40 | 41 | 42 | QQ音乐Cookie 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 0 52 | 53 | 54 | 55 | 56 | 0 57 | 58 | 59 | 0 60 | 61 | 62 | 0 63 | 64 | 65 | 0 66 | 67 | 68 | 69 | 70 | Qt::Vertical 71 | 72 | 73 | 74 | 20 75 | 40 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 登录VIP账号以播放相应歌曲 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 本程序承诺不会以任何形式存储用户密码,请放心使用 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 账号 103 | 104 | 105 | 106 | 107 | 108 | 109 | 手机/邮箱 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 密码 121 | 122 | 123 | 124 | 125 | 126 | 127 | QLineEdit::PasswordEchoOnEdit 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | Qt::Vertical 137 | 138 | 139 | 140 | 20 141 | 40 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 0 152 | 153 | 154 | 0 155 | 156 | 157 | 0 158 | 159 | 160 | 0 161 | 162 | 163 | 164 | 165 | 166 | 167 | 输入浏览器Cookies 168 | 169 | 170 | 171 | 172 | 173 | 174 | Qt::Horizontal 175 | 176 | 177 | 178 | 40 179 | 20 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 帮助 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 登录 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | -------------------------------------------------------------------------------- /order_player/lyricstreamwidget.h: -------------------------------------------------------------------------------- 1 | #ifndef LYRICSTREAMWIDGET_H 2 | #define LYRICSTREAMWIDGET_H 3 | 4 | #include 5 | #include 6 | #include "desktoplyricwidget.h" 7 | 8 | class LyricStreamWidget : public QWidget 9 | { 10 | Q_OBJECT 11 | public: 12 | LyricStreamWidget(QWidget* parent = nullptr) 13 | : QWidget(parent), settings(QApplication::applicationDirPath()+"/musics.ini", QSettings::Format::IniFormat), 14 | updateTimer(new QTimer(this)) 15 | { 16 | setContextMenuPolicy(Qt::CustomContextMenu); 17 | connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(showMenu())); 18 | playingColor = qvariant_cast(settings.value("lyric/playingColor", playingColor)); 19 | waitingColor = qvariant_cast(settings.value("lyric/waitingColor", waitingColor)); 20 | pointSize = settings.value("lyric/pointSize", pointSize).toInt(); 21 | 22 | connect(updateTimer, SIGNAL(timeout()), this, SLOT(update())); 23 | updateTimer->setInterval(16); 24 | } 25 | 26 | void updateFixedHeight() 27 | { 28 | QFont font = this->font(); 29 | font.setPointSize(pointSize); 30 | QFontMetrics fm(font); 31 | this->lineSpacing = fm.height() + (fm.lineSpacing() - fm.height()) * lineSpacingRatio; // 双倍行间距 32 | setFixedHeight((lyricStream.size()+verticalMargin*2) * lineSpacing); 33 | } 34 | 35 | void setLyric(QString text) 36 | { 37 | // 检测是不是全是毫秒还是10毫秒的 38 | int ms10x = 10; 39 | QRegularExpression re10("\\[\\d{2}:\\d{2}(?:\\.([1-9]\\d{2}))?\\]"); 40 | QRegularExpressionMatch match10; 41 | if (text.lastIndexOf(re10, -1, &match10) != -1) 42 | { 43 | int val = match10.captured(1).toInt(); 44 | // qDebug() << " 匹配项:" << match10.capturedTexts() << val; 45 | if (val > 0) // 存在不为0的三位数 46 | { 47 | // qDebug() << "检测到勉强受支持的歌词格式:第三位是毫秒"; 48 | ms10x = 1; 49 | } 50 | } 51 | 52 | // 遍历每一行 53 | QStringList sl = text.split("\n", QString::SkipEmptyParts); 54 | LyricBean prevLyric(false); 55 | qint64 currentTime = 0; 56 | lyricStream.clear(); 57 | foreach (QString line, sl) 58 | { 59 | QRegularExpression re("^\\[(\\d{2}):(\\d{2})(?:\\.(\\d{2,3}))?\\](\\[(\\d{2}):(\\d{2})\\.(\\d{2,3})\\])?(.*)$"); 60 | QRegularExpressionMatch match; 61 | if (line.indexOf(re, 0, &match) == -1) 62 | { 63 | re = QRegularExpression("^\\s*\\[.+:.*\\]\\s*$"); 64 | if (line.indexOf(re) > -1) // 是标签,无视掉 65 | continue; 66 | else if (line.indexOf(QRegularExpression("\\[00:00:00\\](.+)"), 0, &match) > -1) 67 | { 68 | LyricBean lyric; 69 | lyric.start = 0; 70 | lyric.end = 0; 71 | lyric.text = match.captured(1); 72 | lyricStream.append(lyric); 73 | continue; 74 | } 75 | 76 | LyricBean lyric; 77 | lyric.start = currentTime; 78 | lyric.end = 0; 79 | lyric.text = line; 80 | lyricStream.append(lyric); 81 | continue; 82 | } 83 | QStringList caps = match.capturedTexts(); 84 | LyricBean lyric; 85 | int minute = caps.at(1).toInt(); 86 | int second = caps.at(2).toInt(); 87 | int ms10 = caps.at(3).toInt(); 88 | lyric.start = minute * 60000 + second*1000 + ms10 * ms10x; 89 | if (!caps.at(4).isEmpty()) // 有终止时间 90 | { 91 | int minute = caps.at(5).toInt(); 92 | int second = caps.at(6).toInt(); 93 | int ms10 = caps.at(7).toInt(); 94 | lyric.end = minute * 60000 + second*1000 + ms10 * ms10x; 95 | } 96 | lyric.text = caps.at(8); 97 | lyricStream.append(lyric); 98 | currentTime = lyric.start; 99 | } 100 | 101 | currentRow = 0; 102 | updateFixedHeight(); 103 | update(); 104 | } 105 | 106 | /** 107 | * @return 当前行有没有改变 108 | */ 109 | bool setPosition(qint64 position) 110 | { 111 | int prevRow = currentRow; 112 | if (!lyricStream.size()) 113 | return false; 114 | if (currentRow < 0 || currentRow >= lyricStream.size()) 115 | currentRow = 0; 116 | 117 | LyricBean lyric = lyricStream.at(currentRow); 118 | if (currentRow == lyricStream.size()-1 && lyric.start <= position) // 已经到末尾了 119 | return false; 120 | 121 | LyricBean nextLyric = currentRow == lyricStream.size()-1 ? LyricBean(false) : lyricStream.at(currentRow + 1); 122 | if (lyric.start <= position && nextLyric.start >= position) // 不需要改变 123 | return false; 124 | 125 | if (lyric.start > position) // 什么情况?从头强制重新开始! 126 | currentRow = 0; 127 | 128 | while (currentRow+1 < lyricStream.size() && lyricStream.at(currentRow+1).start < position) 129 | { 130 | currentRow++; 131 | } 132 | if (currentRow == prevRow) 133 | return false; 134 | switchRowTimestamp = QDateTime::currentMSecsSinceEpoch(); 135 | updateTimer->start(); 136 | update(); 137 | return true; 138 | } 139 | 140 | int getCurrentTop() const 141 | { 142 | return (currentRow + verticalMargin + lineSpacingRatio / 2 - 1) * lineSpacing; 143 | } 144 | 145 | void setColors(QColor p, QColor w) 146 | { 147 | playingColor = p; 148 | waitingColor = w; 149 | update(); 150 | } 151 | 152 | protected: 153 | void paintEvent(QPaintEvent *event) override 154 | { 155 | QPainter painter(this); 156 | painter.setRenderHint(QPainter::TextAntialiasing, true); 157 | QFont font(painter.font()); 158 | font.setPointSize(pointSize); 159 | painter.setFont(font); 160 | painter.setPen(waitingColor); 161 | QFontMetrics fm(font); 162 | int lineSpacing = fm.height() + (fm.lineSpacing() - fm.height()) * lineSpacingRatio; 163 | double rowOffset = verticalMargin; 164 | qint64 currentTimestamp = QDateTime::currentMSecsSinceEpoch(); 165 | qint64 aniDelta = currentTimestamp - switchRowTimestamp; 166 | bool animating = aniDelta < switchRowDuration; 167 | const double scale = 0.5; 168 | for (int i = 0; i < lyricStream.size(); i++) 169 | { 170 | double row = i + rowOffset; 171 | int top = row * lineSpacing; 172 | if (i == currentRow - 1 && animating) 173 | { 174 | painter.save(); 175 | double prop = scale - scale * aniDelta / switchRowDuration; 176 | QFont font(painter.font()); 177 | font.setPointSize(pointSize * (1+prop)); 178 | painter.setFont(font); 179 | QRect lineRect(0, top - lineSpacing*prop/2, width(), lineSpacing * (1 + prop)); 180 | painter.drawText(lineRect, Qt::AlignCenter, lyricStream.at(i).text); 181 | painter.restore(); 182 | rowOffset += prop; 183 | } 184 | else if (i == currentRow) 185 | { 186 | painter.save(); 187 | if (animating) 188 | { 189 | double prop = scale * aniDelta / switchRowDuration; 190 | QRect lineRect(0, top - lineSpacing*prop/2, width(), lineSpacing * (1 + 2 * prop)); 191 | QFont font(painter.font()); 192 | font.setPointSize(pointSize * (1+prop)); 193 | painter.setPen(playingColor); 194 | painter.setFont(font); 195 | painter.drawText(lineRect, Qt::AlignCenter, lyricStream.at(i).text); 196 | rowOffset += prop; 197 | } 198 | else 199 | { 200 | QRect lineRect(0, top - lineSpacing*0.25, width(), lineSpacing * (1+scale*2)); 201 | QFont font(painter.font()); 202 | font.setPointSize(pointSize * (1+scale)); 203 | painter.setPen(playingColor); 204 | painter.setFont(font); 205 | painter.drawText(lineRect, Qt::AlignCenter, lyricStream.at(i).text); 206 | rowOffset += scale; 207 | 208 | if (updateTimer->isActive()) 209 | updateTimer->stop(); 210 | } 211 | painter.restore(); 212 | } 213 | else 214 | { 215 | QRect lineRect(0, top, width(), lineSpacing); 216 | painter.drawText(lineRect, Qt::AlignCenter, lyricStream.at(i).text); 217 | } 218 | } 219 | 220 | } 221 | 222 | void adjustLyricTime(int begin, int offset) 223 | { 224 | if (!lyricStream.size()) 225 | return ; 226 | 227 | int ms10 = 10; 228 | foreach (LyricBean lyric, lyricStream) 229 | { 230 | if (lyric.start % 10 != 0) 231 | { 232 | ms10 = 1; 233 | qDebug() << "第三位使用毫秒" << lyric.start << lyric.text; 234 | break; 235 | } 236 | } 237 | 238 | QStringList sl; 239 | for (int i = 0; i < lyricStream.size(); i++) 240 | { 241 | LyricBean line = lyricStream.at(i); 242 | if (i >= begin && line.start != 0) 243 | { 244 | line.start += offset; 245 | if (line.end) 246 | line.end += offset; 247 | if (line.start < 0) 248 | line.start = 0; 249 | if (line.end < 0) 250 | line.end = 0; 251 | lyricStream[i] = line; 252 | } 253 | 254 | // 歌词转换回字符串 255 | int minute = line.start / 60000; 256 | int second = line.start % 60000 / 1000; 257 | int msecond = line.start % 1000 / ms10; 258 | QString s = QString("[%1:%2.%3]") 259 | .arg(minute, 2, 10, QLatin1Char('0')) 260 | .arg(second, 2, 10, QLatin1Char('0')) 261 | .arg(msecond, ms10 == 1 ? 3 : 2, 10, QLatin1Char('0')); 262 | if (line.end) 263 | { 264 | int minute = line.end / 60000; 265 | int second = line.end % 60000 / 1000; 266 | int msecond = line.end % 1000 / ms10; 267 | s += QString("[%1:%2.%3]") 268 | .arg(minute, 2, 10, QLatin1Char('0')) 269 | .arg(second, 2, 10, QLatin1Char('0')) 270 | .arg(msecond, ms10 == 1 ? 3 : 2, 10, QLatin1Char('0')); 271 | } 272 | s += line.text; 273 | sl << s; 274 | } 275 | emit signalAdjustLyricTime(sl.join("\n")); 276 | } 277 | 278 | signals: 279 | void signalSwitchCoverBlur(); 280 | void signalAdjustLyricTime(QString); 281 | 282 | private slots: 283 | void showMenu() 284 | { 285 | FacileMenu* menu = new FacileMenu(this); 286 | menu->addAction("已播放颜色", [=]{ 287 | QColor c = QColorDialog::getColor(playingColor, this, "选择已播放颜色", QColorDialog::ShowAlphaChannel); 288 | if (!c.isValid()) 289 | return ; 290 | if (c != playingColor) 291 | { 292 | settings.setValue("lyric/playingColor", playingColor = c); 293 | update(); 294 | } 295 | })->fgColor(playingColor); 296 | menu->addAction("未播放颜色", [=]{ 297 | QColor c = QColorDialog::getColor(waitingColor, this, "选择未播放颜色", QColorDialog::ShowAlphaChannel); 298 | if (!c.isValid()) 299 | return ; 300 | if (c != waitingColor) 301 | { 302 | settings.setValue("lyric/waitingColor", waitingColor = c); 303 | update(); 304 | } 305 | })->fgColor(waitingColor); 306 | auto fontMenu = menu->addMenu("字体大小"); 307 | QStringList sl; 308 | for (int i = 8; i < 30; i++) 309 | sl << QString::number(i); 310 | fontMenu->addOptions(sl, pointSize-8, [=](int index){ 311 | pointSize = index + 8; 312 | settings.setValue("lyric/pointSize", pointSize); 313 | updateFixedHeight(); 314 | update(); 315 | }); 316 | auto timeMenu = menu->addMenu("时间微调"); 317 | timeMenu->addAction("加快5秒", [=]{adjustLyricTime(0, -5000);}); 318 | timeMenu->addAction("加快2秒", [=]{adjustLyricTime(0, -2000);}); 319 | timeMenu->addAction("加快1秒", [=]{adjustLyricTime(0, -1000);}); 320 | timeMenu->addAction("加快0.5秒", [=]{adjustLyricTime(0, -500);}); 321 | timeMenu->addAction("加快0.2秒", [=]{adjustLyricTime(0, -200);}); 322 | timeMenu->addAction("加快0.1秒", [=]{adjustLyricTime(0, -100);}); 323 | timeMenu->addAction("减慢0.1秒", [=]{adjustLyricTime(0, +100);}); 324 | timeMenu->addAction("减慢0.2秒", [=]{adjustLyricTime(0, +200);}); 325 | timeMenu->addAction("减慢0.5秒", [=]{adjustLyricTime(0, +500);}); 326 | timeMenu->addAction("减慢1秒", [=]{adjustLyricTime(0, +1000);}); 327 | timeMenu->addAction("减慢2秒", [=]{adjustLyricTime(0, +2000);}); 328 | timeMenu->addAction("减慢5秒", [=]{adjustLyricTime(0, +5000);}); 329 | menu->exec(QCursor::pos()); 330 | } 331 | 332 | private: 333 | QSettings settings; 334 | LyricStream lyricStream; 335 | int currentRow = -1; 336 | qint64 switchRowTimestamp = 0; 337 | const int switchRowDuration = 200; 338 | QTimer* updateTimer; 339 | int lineSpacing = 30; 340 | int verticalMargin = 5; // 上下间距5行 341 | 342 | const int lineSpacingRatio = 8; 343 | int pointSize = 12; 344 | QColor playingColor = QColor(155, 80, 255); 345 | QColor waitingColor = Qt::black; 346 | }; 347 | 348 | #endif // LYRICSTREAMWIDGET_H 349 | 350 | -------------------------------------------------------------------------------- /order_player/numberanimation.cpp: -------------------------------------------------------------------------------- 1 | #include "numberanimation.h" 2 | 3 | NumberAnimation::NumberAnimation(QWidget *parent) : NumberAnimation("", Qt::red, parent) 4 | { 5 | } 6 | 7 | NumberAnimation::NumberAnimation(QString text, QColor color, QWidget *parent) : QLabel(parent) 8 | { 9 | endProp = 0.5; 10 | fontSize = font().pointSize(); 11 | setAlignment(Qt::AlignCenter); 12 | 13 | setText(text); 14 | setColor(color); 15 | 16 | show(); 17 | } 18 | 19 | void NumberAnimation::setCenter(QPoint p) 20 | { 21 | move(p.x() - width() / 2, p.y() - height()); 22 | } 23 | 24 | void NumberAnimation::setText(QString text) 25 | { 26 | QLabel::setText(text); 27 | } 28 | 29 | void NumberAnimation::setColor(QColor color) 30 | { 31 | this->color = color; 32 | setAlpha(255); 33 | } 34 | 35 | void NumberAnimation::setFontSize(int f) 36 | { 37 | fontSize = f; 38 | 39 | QFont font = this->font(); 40 | font.setPointSize(f); 41 | font.setBold(true); 42 | setFont(font); 43 | } 44 | 45 | void NumberAnimation::setEndProp(double prop) 46 | { 47 | endProp = prop; 48 | } 49 | 50 | int NumberAnimation::getFontSize() 51 | { 52 | return fontSize; 53 | } 54 | 55 | void NumberAnimation::setAlpha(int a) 56 | { 57 | this->alpha = a; 58 | QColor c = color; 59 | color.setAlpha(a); 60 | 61 | setStyleSheet("color:" + QVariant(c).toString()); 62 | } 63 | 64 | int NumberAnimation::getAlpha() 65 | { 66 | return alpha; 67 | } 68 | 69 | void NumberAnimation::startAnimation() 70 | { 71 | int duration = 500; 72 | 73 | QPropertyAnimation *ani = new QPropertyAnimation(this, "fontSize"); 74 | ani->setStartValue(fontSize); 75 | ani->setEndValue(fontSize * endProp); 76 | ani->setDuration(duration); 77 | ani->setEasingCurve(QEasingCurve::InCubic); 78 | connect(ani, SIGNAL(finished()), ani, SLOT(deleteLater())); 79 | ani->start(); 80 | 81 | QPropertyAnimation *ani2 = new QPropertyAnimation(this, "pos"); 82 | ani2->setStartValue(pos()); 83 | ani2->setEndValue(QPoint(pos().x(), pos().y() - fontSize * 2)); 84 | ani2->setDuration(duration); 85 | ani2->setEasingCurve(QEasingCurve::OutCubic); 86 | connect(ani2, SIGNAL(finished()), ani2, SLOT(deleteLater())); 87 | connect(ani2, SIGNAL(finished()), this, SLOT(deleteLater())); 88 | ani2->start(); 89 | 90 | QPropertyAnimation *ani3 = new QPropertyAnimation(this, "alpha"); 91 | ani3->setStartValue(255); 92 | ani3->setEndValue(0x0); 93 | ani3->setDuration(duration); 94 | ani3->setEasingCurve(QEasingCurve::InCubic); 95 | connect(ani3, SIGNAL(finished()), ani3, SLOT(deleteLater())); 96 | ani3->start(); 97 | } 98 | -------------------------------------------------------------------------------- /order_player/numberanimation.h: -------------------------------------------------------------------------------- 1 | #ifndef NUMBERANIMATION_H 2 | #define NUMBERANIMATION_H 3 | 4 | #include 5 | #include 6 | 7 | class NumberAnimation : public QLabel 8 | { 9 | Q_OBJECT 10 | Q_PROPERTY(int fontSize READ getFontSize WRITE setFontSize) 11 | Q_PROPERTY(int alpha READ getAlpha WRITE setAlpha) 12 | public: 13 | NumberAnimation(QWidget *parent = nullptr); 14 | NumberAnimation(QString text, QColor color = Qt::red, QWidget *parent = nullptr); 15 | 16 | void setCenter(QPoint p); 17 | void setText(QString text); 18 | void setColor(QColor color); 19 | void setEndProp(double prop); 20 | 21 | void setFontSize(int f); 22 | int getFontSize(); 23 | 24 | void setAlpha(int a); 25 | int getAlpha(); 26 | 27 | void startAnimation(); 28 | 29 | private: 30 | int fontSize; // 字体大小动画变量 31 | int alpha; 32 | QColor color; 33 | double endProp; // 最终大小比例 34 | }; 35 | 36 | #endif // NUMBERANIMATION_H 37 | -------------------------------------------------------------------------------- /order_player/orderplayerwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef ORDERPLAYERWINDOW_H 2 | #define ORDERPLAYERWINDOW_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 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "facilemenu.h" 27 | #include "songbeans.h" 28 | #include "desktoplyricwidget.h" 29 | #include "numberanimation.h" 30 | #include "imageutil.h" 31 | #include "logindialog.h" 32 | 33 | QT_BEGIN_NAMESPACE 34 | namespace Ui { class OrderPlayerWindow; } 35 | QT_END_NAMESPACE 36 | 37 | QT_BEGIN_NAMESPACE 38 | extern Q_WIDGETS_EXPORT void qt_blurImage( QPainter *p, QImage &blurImage, qreal radius, bool quality, bool alphaOnly, int transposed = 0 ); 39 | QT_END_NAMESPACE 40 | 41 | #define LISTTAB_ORDER 0 42 | #define LISTTAB_NORMAL 1 43 | #define LISTTAB_FAVORITE 2 44 | #define LISTTAB_PLAYLIST 3 45 | #define LISTTAB_HISTORY 3 46 | 47 | #define MUSIC_DEB if (0) qDebug() 48 | 49 | typedef std::function const NetStringFunc; 50 | typedef std::function const NetJsonFunc; 51 | typedef std::function const NetReplyFunc; 52 | 53 | enum MusicQuality 54 | { 55 | NormalQuality, // MP3普通品质 56 | HightQuality, // MP3高品质 57 | ApeQuality, // 高品无损 58 | FlacQuality // 无损 59 | }; 60 | 61 | struct BFSColor 62 | { 63 | int v[12] = {0}; 64 | 65 | BFSColor() 66 | {} 67 | 68 | BFSColor(QList cs) 69 | { 70 | Q_ASSERT(cs.size() == 4); 71 | for (int i = 0; i < 4; i++) 72 | { 73 | QColor c = cs[i]; 74 | v[i*3+0] = c.red(); 75 | v[i*3+1] = c.green(); 76 | v[i*3+2] = c.blue(); 77 | } 78 | } 79 | 80 | BFSColor operator-(const BFSColor& ano) 81 | { 82 | BFSColor bfs; 83 | for (int i = 0; i < 12; i++) 84 | bfs.v[i] = this->v[i] - ano.v[i]; 85 | return bfs; 86 | } 87 | 88 | BFSColor operator+(const BFSColor& ano) 89 | { 90 | BFSColor bfs; 91 | for (int i = 0; i < 12; i++) 92 | bfs.v[i] = this->v[i] + ano.v[i]; 93 | return bfs; 94 | } 95 | 96 | BFSColor operator*(const double& prop) 97 | { 98 | BFSColor bfs; 99 | for (int i = 0; i < 12; i++) 100 | bfs.v[i] = int(this->v[i] * prop); 101 | return bfs; 102 | } 103 | 104 | static BFSColor fromPalette(QPalette pa) 105 | { 106 | QColor bg = pa.color(QPalette::Window); 107 | QColor fg = pa.color(QPalette::Text); 108 | QColor sbg = pa.color(QPalette::Highlight); 109 | QColor sfg = pa.color(QPalette::HighlightedText); 110 | return BFSColor(QList{bg, fg, sbg, sfg}); 111 | } 112 | 113 | void toColors(QColor* bg, QColor* fg, QColor* sbg, QColor* sfg) 114 | { 115 | *bg = QColor(v[0], v[1], v[2]); 116 | *fg = QColor(v[3], v[4], v[5]); 117 | *sbg = QColor(v[6], v[7], v[8]); 118 | *sfg = QColor(v[9], v[10], v[11]); 119 | } 120 | }; 121 | 122 | class OrderPlayerWindow : public QMainWindow 123 | { 124 | Q_OBJECT 125 | Q_PROPERTY(int lyricScroll READ getLyricScroll WRITE setLyricScroll) 126 | Q_PROPERTY(int disappearBgProg READ getDisappearBgProg WRITE setDisappearBgProg) 127 | Q_PROPERTY(int appearBgProg READ getAppearBgProg WRITE setAppearBgProg) 128 | Q_PROPERTY(double paletteProg READ getPaletteBgProg WRITE setPaletteBgProg) 129 | public: 130 | OrderPlayerWindow(QWidget *parent = nullptr); 131 | ~OrderPlayerWindow() override; 132 | 133 | enum PlayCircleMode 134 | { 135 | OrderList, 136 | SingleCircle 137 | }; 138 | 139 | const SongList& getOrderSongs() const; 140 | bool hasSongInOrder(QString by); 141 | 142 | public slots: 143 | void slotSearchAndAutoAppend(QString key, QString by = ""); 144 | void improveUserSongByOrder(QString username, int promote); 145 | void cutSongIfUser(QString username); 146 | void cutSong(); 147 | 148 | private slots: 149 | void on_searchEdit_returnPressed(); 150 | 151 | void on_searchButton_clicked(); 152 | 153 | void on_searchResultTable_cellActivated(int row, int); 154 | 155 | void on_searchResultTable_customContextMenuRequested(const QPoint &); 156 | 157 | void on_listTabWidget_currentChanged(int index); 158 | 159 | void sortSearchResult(int col); 160 | 161 | void on_playProgressSlider_sliderReleased(); 162 | 163 | void on_playProgressSlider_sliderMoved(int position); 164 | 165 | void on_volumeSlider_sliderMoved(int position); 166 | 167 | void on_playButton_clicked(); 168 | 169 | void on_volumeButton_clicked(); 170 | 171 | void on_circleModeButton_clicked(); 172 | 173 | void slotSongPlayEnd(); 174 | 175 | void on_orderSongsListView_customContextMenuRequested(const QPoint &); 176 | 177 | void on_favoriteSongsListView_customContextMenuRequested(const QPoint &); 178 | 179 | void on_historySongsListView_customContextMenuRequested(const QPoint &); 180 | 181 | void on_listSongsListView_customContextMenuRequested(const QPoint &); 182 | 183 | void on_normalSongsListView_customContextMenuRequested(const QPoint &); 184 | 185 | void on_orderSongsListView_activated(const QModelIndex &index); 186 | 187 | void on_favoriteSongsListView_activated(const QModelIndex &index); 188 | 189 | void on_normalSongsListView_activated(const QModelIndex &index); 190 | 191 | void on_historySongsListView_activated(const QModelIndex &index); 192 | 193 | void on_desktopLyricButton_clicked(); 194 | 195 | void slotExpandPlayingButtonClicked(); 196 | 197 | void slotPlayerPositionChanged(); 198 | 199 | void on_splitter_splitterMoved(int, int); 200 | 201 | void on_titleButton_clicked(); 202 | 203 | void adjustCurrentLyricTime(QString lyric); 204 | 205 | void on_settingsButton_clicked(); 206 | 207 | void on_musicSourceButton_clicked(); 208 | 209 | void on_nextSongButton_clicked(); 210 | 211 | private: 212 | void searchMusic(QString key, QString addBy = QString(), bool notify = false); 213 | void searchMusicBySource(QString key, MusicSource source, QString addBy = QString()); 214 | void setSearchResultTable(SongList songs); 215 | void setSearchResultTable(PlayListList playLists); 216 | void addFavorite(SongList songs); 217 | void removeFavorite(SongList songs); 218 | void addNormal(SongList songs); 219 | void removeNormal(SongList songs); 220 | void removeOrder(SongList songs); 221 | void saveSongList(QString key, const SongList &songs); 222 | void restoreSongList(QString key, SongList& songs); 223 | void setSongModelToView(const SongList& songs, QListView* listView); 224 | QString songPath(const Song &song) const; 225 | QString lyricPath(const Song &song) const; 226 | QString coverPath(const Song &song) const; 227 | bool isSongDownloaded(Song song); 228 | QString msecondToString(qint64 msecond); 229 | void activeSong(Song song); 230 | bool isNotPlaying() const; 231 | Song getSuiableSong(QString key) const; 232 | 233 | void startPlaySong(Song song); 234 | void playNext(); 235 | void appendOrderSongs(SongList songs); 236 | void appendNextSongs(SongList songs); 237 | 238 | void playLocalSong(Song song); 239 | void addDownloadSong(Song song); 240 | void downloadNext(); 241 | void downloadSong(Song song); 242 | void downloadSongLyric(Song song); 243 | void downloadSongCover(Song song); 244 | void setCurrentLyric(QString lyric); 245 | void openPlayList(QString shareUrl); 246 | void clearDownloadFiles(); 247 | void clearHoaryFiles(); 248 | 249 | void adjustExpandPlayingButton(); 250 | void connectDesktopLyricSignals(); 251 | void setCurrentCover(const QPixmap& pixmap); 252 | void setBlurBackground(const QPixmap& bg); 253 | void startBgAnimation(int duration = 2000); 254 | void setThemeColor(const QPixmap& cover); 255 | 256 | void readMp3Data(const QByteArray& array); 257 | 258 | void setMusicIconBySource(); 259 | 260 | void fetch(QString url, NetStringFunc func, MusicSource cookie = UnknowMusic); 261 | void fetch(QString url, NetJsonFunc func, MusicSource cookie = UnknowMusic); 262 | void fetch(QString url, NetReplyFunc func, MusicSource cookie = UnknowMusic); 263 | void fetch(QString url, QStringList params, NetJsonFunc func, MusicSource cookie = UnknowMusic); 264 | QVariant getCookies(QString cookieString); 265 | 266 | protected: 267 | void showEvent(QShowEvent*e) override; 268 | void closeEvent(QCloseEvent*) override; 269 | void resizeEvent(QResizeEvent*) override; 270 | void paintEvent(QPaintEvent* e) override; 271 | 272 | private: 273 | void setLyricScroll(int x); 274 | int getLyricScroll() const; 275 | void setAppearBgProg(int x); 276 | int getAppearBgProg() const; 277 | void setDisappearBgProg(int x); 278 | int getDisappearBgProg() const; 279 | void showTabAnimation(QPoint center, QString text); 280 | void setPaletteBgProg(double x); 281 | double getPaletteBgProg() const; 282 | 283 | signals: 284 | void signalSongDownloadFinished(Song song); 285 | void signalLyricDownloadFinished(Song song); 286 | void signalCoverDownloadFinished(Song song); 287 | void signalSongPlayStarted(Song song); 288 | void signalSongPlayFinished(Song song); 289 | 290 | // 因为目标是作为插件,这些信号是给外面程序连接的 291 | void signalOrderSongSucceed(Song song, qint64 msecond, int waiting); 292 | void signalOrderSongPlayed(Song song); 293 | void signalOrderSongModified(const SongList& songs); 294 | void signalOrderSongNoCopyright(Song song); 295 | void signalOrderSongImproved(Song song, int previous, int current); 296 | void signalOrderSongCutted(Song song); 297 | void signalOrderSongStarted(); 298 | void signalOrderSongEnded(); 299 | 300 | void signalWindowClosed(); 301 | 302 | private: 303 | Ui::OrderPlayerWindow *ui; 304 | bool starting = true; 305 | QSettings settings; 306 | QDir musicsFileDir; 307 | MusicSource musicSource = NeteaseCloudMusic; 308 | SongList searchResultSongs; 309 | PlayListList searchResultPlayLists; 310 | 311 | SongList orderSongs; 312 | SongList favoriteSongs; 313 | SongList normalSongs; 314 | SongList historySongs; 315 | PlayListList myPlayLists; 316 | SongList toDownloadSongs; 317 | Song playAfterDownloaded; 318 | Song downloadingSong; 319 | 320 | QMediaPlayer* player; 321 | PlayCircleMode circleMode = OrderList; 322 | Song playingSong; 323 | int lyricScroll; 324 | 325 | bool doubleClickToPlay = false; // 双击是立即播放,还是添加到列表 326 | qint64 setPlayPositionAfterLoad = 0; // 加载后跳转到时间 327 | 328 | DesktopLyricWidget* desktopLyric; 329 | InteractiveButtonBase* expandPlayingButton; 330 | 331 | Song coveringSong; 332 | bool blurBg = true; 333 | int blurAlpha = 32; 334 | QPixmap currentCover; 335 | int currentBgAlpha = 255; 336 | QPixmap currentBlurBg; 337 | QPixmap prevBlurBg; 338 | int prevBgAlpha = 0; 339 | 340 | bool themeColor = false; 341 | BFSColor prevPa; 342 | BFSColor currentPa; 343 | double paletteAlpha; 344 | 345 | QString currentResultOrderBy; // 当前搜索结果是谁点的歌,用作替换 346 | Song prevOrderSong; 347 | bool autoSwitchSource = true; // 自动切换音源 348 | bool insertOrderOnce = false; // 插入到前面 349 | 350 | int songBr = 320000; // 码率,单位bps 351 | QString neteaseCookies; 352 | QVariant neteaseCookiesVariant; 353 | QString qqmusicCookies; 354 | QVariant qqmusicCookiesVariant; 355 | bool unblockQQMusic = false; 356 | }; 357 | 358 | class NoFocusDelegate : public QStyledItemDelegate 359 | { 360 | Q_OBJECT 361 | public: 362 | NoFocusDelegate(){} 363 | NoFocusDelegate(QAbstractItemView* view, int columnCount = 1) : view(view), columnLast(columnCount-1) 364 | { 365 | if (view) 366 | { 367 | view->setItemDelegate(this); 368 | // 下面代码开启选中项圆角,奈何太丑了,关掉了 369 | // connect(view, SIGNAL(itemSelectionChanged()), this, SLOT(selectionChanged())); 370 | } 371 | } 372 | 373 | ~NoFocusDelegate(){} 374 | 375 | void paint(QPainter* painter, const QStyleOptionViewItem & option, const QModelIndex &index) const 376 | { 377 | QStyleOptionViewItem itemOption(option); 378 | if (itemOption.state & QStyle::State_Selected) 379 | { 380 | if (selectTop == -1) // 没有选中,或者判断失误? 381 | { 382 | painter->fillRect(option.rect, QApplication::palette().color(QPalette::Highlight)); 383 | } 384 | else // 绘制圆角矩形 385 | { 386 | QRect rect = option.rect; 387 | const int radius = qMin(5, qMin(rect.width(), rect.height())/3); 388 | const int row = index.row(); 389 | 390 | QPainterPath path; 391 | path.setFillRule(Qt::FillRule::WindingFill); 392 | if (columnLast == 0) 393 | { 394 | if (row == selectTop) 395 | path.addRoundedRect(rect.left(), rect.top(), rect.width()-1, radius*2, radius, radius); 396 | else 397 | path.addRect(rect.left(), rect.top(), rect.width()-1, radius*2); 398 | if (row == selectBottom) 399 | path.addRoundedRect(rect.left(), rect.bottom() - radius*2, rect.width()-1, radius*2, radius, radius); 400 | else 401 | path.addRect(rect.left(), rect.bottom() - radius*2, rect.width()-1, radius*2+1); 402 | path.addRect(rect.left(), rect.top()+radius, rect.width()-1, rect.height()-radius*2); 403 | } 404 | else 405 | { 406 | path.addRect(rect); 407 | 408 | if (row == selectTop) 409 | { 410 | QPainterPath cornerPath; 411 | if (index.column() == 0) 412 | { 413 | QPainterPath path1, path2; 414 | path1.addRect(rect.left(), rect.top(), radius, radius); 415 | path2.addEllipse(rect.left(), rect.top(), radius*2+2, radius*2+2); 416 | path -= path1; 417 | path += path2; 418 | } 419 | if (index.column() == columnLast) 420 | { 421 | QPainterPath path1, path2; 422 | path1.addRect(rect.right()-radius, rect.top(), radius+1, radius); 423 | path2.addEllipse(rect.right()-radius*2, rect.top(), radius*2, radius*2); 424 | path -= path1; 425 | path += path2; 426 | } 427 | } 428 | if (row == selectBottom) 429 | { 430 | if (index.column() == 0) 431 | { 432 | QPainterPath path1, path2; 433 | path1.addRect(rect.left(), rect.bottom()-radius, radius, radius+1); 434 | path2.addEllipse(rect.left(), rect.bottom()-radius*2-2, radius*2+2, radius*2+2); 435 | path -= path1; 436 | path += path2; 437 | } 438 | if (index.column() == columnLast) 439 | { 440 | QPainterPath path1, path2; 441 | path1.addRect(rect.right()-radius, rect.bottom()-radius, radius+1, radius+1); 442 | path2.addEllipse(rect.right()-radius*2, rect.bottom()-radius*2, radius*2, radius*2); 443 | path -= path1; 444 | path += path2; 445 | } 446 | } 447 | } 448 | painter->setRenderHint(QPainter::Antialiasing); 449 | painter->fillPath(path, QApplication::palette().color(QPalette::Highlight)); 450 | } 451 | } 452 | 453 | QRect rect = option.rect; 454 | rect.setLeft(rect.left() + 4); 455 | painter->setPen(QApplication::palette().color(QPalette::Text)); 456 | painter->drawText(rect, Qt::AlignLeft | Qt::AlignVCenter, index.data(Qt::DisplayRole).toString()); 457 | } 458 | 459 | private slots: 460 | void selectionChanged() 461 | { 462 | QList indexes = view->selectionModel()->selectedRows(0); 463 | if (!indexes.size()) 464 | { 465 | selectTop = selectBottom = -1; 466 | return ; 467 | } 468 | else if (indexes.size() == 1) 469 | { 470 | selectTop = selectBottom = indexes.first().row(); 471 | return ; 472 | } 473 | 474 | selectTop = view->model()->rowCount(); 475 | selectBottom = -1; 476 | foreach (const QModelIndex index, indexes) 477 | { 478 | int row = index.row(); 479 | if (row < selectTop) 480 | selectTop = row; 481 | if (row > selectBottom) 482 | selectBottom = row; 483 | } 484 | } 485 | 486 | private: 487 | QAbstractItemView* view = nullptr; 488 | int columnLast = 1; 489 | int selectTop = -1; 490 | int selectBottom = -1; 491 | }; 492 | 493 | #endif // ORDERPLAYERWINDOW_H 494 | -------------------------------------------------------------------------------- /order_player/orderplayerwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | OrderPlayerWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 956 10 | 642 11 | 12 | 13 | 14 | 雪以音乐 15 | 16 | 17 | 18 | :/snow/snow3:/snow/snow3 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 0 29 | 30 | 31 | 32 | 33 | text-align: left; 34 | 35 | 36 | 雪以音乐 37 | 38 | 39 | 40 | :/snow/snow3:/snow/snow3 41 | 42 | 43 | true 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 0 52 | 0 53 | 54 | 55 | 56 | 57 | 20 58 | 24 59 | 60 | 61 | 62 | #widget 63 | { 64 | border: none; 65 | border-radius: 12px; 66 | background: rgba(255, 255, 255, 128); 67 | } 68 | 69 | 70 | 71 | 11 72 | 73 | 74 | 2 75 | 76 | 77 | 2 78 | 79 | 80 | 81 | 82 | background: transparent; 83 | 84 | 85 | 86 | 87 | 88 | 89 | :/musics/netease:/musics/netease 90 | 91 | 92 | true 93 | 94 | 95 | 96 | 97 | 98 | 99 | QLineEdit 100 | { 101 | border: none; 102 | background: transparent; 103 | } 104 | 105 | 106 | 107 | 108 | 109 | 110 | background: transparent; 111 | 112 | 113 | 114 | 115 | 116 | 117 | :/icons/search:/icons/search 118 | 119 | 120 | true 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 0 132 | 133 | 134 | 0 135 | 136 | 137 | 0 138 | 139 | 140 | 0 141 | 142 | 143 | 0 144 | 145 | 146 | 147 | 148 | Qt::Horizontal 149 | 150 | 151 | 152 | 40 153 | 20 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | :/icons/settings:/icons/settings 166 | 167 | 168 | true 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 0 182 | 0 183 | 184 | 185 | 186 | QWidget 187 | { 188 | background: transparent; 189 | } 190 | 191 | 192 | Qt::Horizontal 193 | 194 | 195 | 196 | 197 | 0 198 | 199 | 200 | 0 201 | 202 | 203 | 204 | 205 | true 206 | 207 | 208 | QTabWidget 209 | { 210 | background: transparent; 211 | border: none; 212 | } 213 | 214 | QTabWidget::tab-bar 215 | { 216 | background: transparent; 217 | border: none; 218 | } 219 | 220 | QTabWidget::pane 221 | { 222 | background: transparent; 223 | border: none; 224 | } 225 | 226 | QTabBar::tab 227 | { 228 | background: transparent; 229 | border-radius: 5px; 230 | padding: 0px; 231 | padding-left:16px; 232 | padding-top: 6px; 233 | padding-bottom: 4px; 234 | } 235 | 236 | QTabBar::tab:hover 237 | { 238 | background: rgba(255,255,255,64); 239 | } 240 | 241 | QTabBar::tab:selected 242 | { 243 | background: rgba(255,255,255,128); 244 | } 245 | 246 | QTabBar::tab:disabled 247 | { 248 | width: 0; 249 | color: transparent; 250 | tab-image: none; 251 | } 252 | 253 | 254 | QTabWidget::Rounded 255 | 256 | 257 | 0 258 | 259 | 260 | 261 | QWidget 262 | { 263 | background: transparent; 264 | border: none; 265 | } 266 | 267 | 268 | 269 | :/icons/order:/icons/order 270 | 271 | 272 | 273 | 274 | 275 | 点歌:根据弹幕自动搜索添加 276 | 277 | 278 | 279 | 0 280 | 281 | 282 | 0 283 | 284 | 285 | 0 286 | 287 | 288 | 0 289 | 290 | 291 | 292 | 293 | Qt::CustomContextMenu 294 | 295 | 296 | #orderSongsListView 297 | { 298 | background: transparent; 299 | border: none; 300 | } 301 | 302 | 303 | QAbstractItemView::NoEditTriggers 304 | 305 | 306 | QAbstractItemView::ExtendedSelection 307 | 308 | 309 | QAbstractItemView::SelectRows 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | QWidget 318 | { 319 | background: transparent; 320 | border: none; 321 | } 322 | 323 | 324 | 325 | :/icons/random:/icons/random 326 | 327 | 328 | 329 | 330 | 331 | 固定:没有点歌列表时,随机播放里面的歌曲 332 | 333 | 334 | 335 | 0 336 | 337 | 338 | 0 339 | 340 | 341 | 0 342 | 343 | 344 | 0 345 | 346 | 347 | 348 | 349 | Qt::CustomContextMenu 350 | 351 | 352 | #playHistoriesListView 353 | { 354 | background: transparent; 355 | border: none; 356 | } 357 | 358 | 359 | QAbstractItemView::NoEditTriggers 360 | 361 | 362 | QAbstractItemView::ExtendedSelection 363 | 364 | 365 | QAbstractItemView::SelectRows 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | QWidget 374 | { 375 | background: transparent; 376 | border: none; 377 | } 378 | 379 | 380 | 381 | :/icons/favorite:/icons/favorite 382 | 383 | 384 | 385 | 386 | 387 | 收藏 388 | 389 | 390 | 391 | 0 392 | 393 | 394 | 0 395 | 396 | 397 | 0 398 | 399 | 400 | 0 401 | 402 | 403 | 404 | 405 | Qt::CustomContextMenu 406 | 407 | 408 | #favoriteSongsListView 409 | { 410 | background: transparent; 411 | border: none; 412 | } 413 | 414 | 415 | 416 | 417 | 418 | 419 | QAbstractItemView::EditKeyPressed 420 | 421 | 422 | QAbstractItemView::ExtendedSelection 423 | 424 | 425 | QAbstractItemView::SelectRows 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | true 434 | 435 | 436 | QWidget 437 | { 438 | background: transparent; 439 | border: none; 440 | } 441 | 442 | 443 | 444 | :/icons/playlist:/icons/playlist 445 | 446 | 447 | 448 | 449 | 450 | 歌单 451 | 452 | 453 | 454 | 0 455 | 456 | 457 | 0 458 | 459 | 460 | 0 461 | 462 | 463 | 0 464 | 465 | 466 | 467 | 468 | true 469 | 470 | 471 | Qt::CustomContextMenu 472 | 473 | 474 | #playHistoriesListView 475 | { 476 | background: transparent; 477 | border: none; 478 | } 479 | 480 | 481 | QAbstractItemView::NoEditTriggers 482 | 483 | 484 | QAbstractItemView::ExtendedSelection 485 | 486 | 487 | QAbstractItemView::SelectRows 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | QWidget 496 | { 497 | background: transparent; 498 | border: none; 499 | } 500 | 501 | 502 | 503 | :/icons/history:/icons/history 504 | 505 | 506 | 507 | 508 | 509 | 历史 510 | 511 | 512 | 513 | 0 514 | 515 | 516 | 0 517 | 518 | 519 | 0 520 | 521 | 522 | 0 523 | 524 | 525 | 526 | 527 | Qt::CustomContextMenu 528 | 529 | 530 | #playHistoriesListView 531 | { 532 | background: transparent; 533 | border: none; 534 | } 535 | 536 | 537 | QAbstractItemView::NoEditTriggers 538 | 539 | 540 | QAbstractItemView::ExtendedSelection 541 | 542 | 543 | QAbstractItemView::SelectRows 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 50 558 | 50 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 0 570 | 571 | 572 | 2 573 | 574 | 575 | 2 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | QLabel{ 588 | color:gray; 589 | } 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | Qt::LeftToRight 605 | 606 | 607 | #bodyStackWidget 608 | { 609 | background: transparent; 610 | border: none; 611 | } 612 | 613 | 614 | 1 615 | 616 | 617 | 618 | 619 | 5 620 | 621 | 622 | 0 623 | 624 | 625 | 0 626 | 627 | 628 | 0 629 | 630 | 631 | 632 | 633 | Qt::CustomContextMenu 634 | 635 | 636 | QTableWidget 637 | { 638 | background: transparent; 639 | border: none; 640 | alternate-background-color: rgba(132, 136, 139, 20); 641 | } 642 | 643 | QHeaderView::section 644 | { 645 | background-color: transparent; 646 | } 647 | 648 | QTableCornerButton::section 649 | { 650 | background:transparent; 651 | } 652 | 653 | 654 | QAbstractItemView::EditKeyPressed 655 | 656 | 657 | true 658 | 659 | 660 | QAbstractItemView::ExtendedSelection 661 | 662 | 663 | QAbstractItemView::SelectRows 664 | 665 | 666 | QAbstractItemView::ScrollPerPixel 667 | 668 | 669 | QAbstractItemView::ScrollPerPixel 670 | 671 | 672 | false 673 | 674 | 675 | true 676 | 677 | 678 | false 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | #lyricsPage 687 | { 688 | background: transparent; 689 | } 690 | 691 | 692 | 693 | 5 694 | 695 | 696 | 0 697 | 698 | 699 | 0 700 | 701 | 702 | 0 703 | 704 | 705 | 706 | 707 | Qt::CustomContextMenu 708 | 709 | 710 | #scrollArea 711 | { 712 | background: transparent; 713 | border: none; 714 | } 715 | 716 | 717 | true 718 | 719 | 720 | 721 | 722 | 0 723 | 0 724 | 456 725 | 514 726 | 727 | 728 | 729 | #lyricWidget 730 | { 731 | background: transparent; 732 | } 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | :/icons/play:/icons/play 752 | 753 | 754 | true 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | :/icons/next_song:/icons/next_song 766 | 767 | 768 | true 769 | 770 | 771 | 772 | 773 | 774 | 775 | 0:0 776 | 777 | 778 | 779 | 780 | 781 | 782 | QSlider{ border-color: #bcbcbc; } 783 | QSlider::groove:horizontal { border: 1px solid #999999; height: 1px; margin: 0px 0; left: 5px; right: 5px; } 784 | QSlider::handle:horizontal { border: 0px ; border-image: url(:/snow/snow4); width: 16px; margin: -8px -7px -8px -7px; } 785 | QSlider::add-page:horizontal{ background: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 #bcbcbc, stop:0.25 #bcbcbc, stop:0.5 #bcbcbc, stop:1 #bcbcbc); } 786 | QSlider::sub-page:horizontal{ background: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 #439cf3, stop:0.25 #439cf3, stop:0.5 #439cf3, stop:1 #439cf3); } 787 | 788 | 789 | Qt::Horizontal 790 | 791 | 792 | 793 | 794 | 795 | 796 | 2:30 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | :/icons/volume:/icons/volume 808 | 809 | 810 | true 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 120 819 | 16777215 820 | 821 | 822 | 823 | QSlider{ border-color: #bcbcbc; } 824 | QSlider::groove:horizontal { border: 1px solid #999999; height: 1px; margin: 0px 0; left: 5px; right: 5px; } 825 | QSlider::handle:horizontal { border: 0px ; border-image: url(:/snow/snow4); width: 16px; margin: -8px -7px -8px -7px; } 826 | QSlider::add-page:horizontal{ background: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 #bcbcbc, stop:0.25 #bcbcbc, stop:0.5 #bcbcbc, stop:1 #bcbcbc); } 827 | QSlider::sub-page:horizontal{ background: qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, stop:0 #439cf3, stop:0.25 #439cf3, stop:0.5 #439cf3, stop:1 #439cf3); } 828 | 829 | 830 | 100 831 | 832 | 833 | 10 834 | 835 | 836 | 50 837 | 838 | 839 | Qt::Horizontal 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | :/icons/order_list:/icons/order_list 851 | 852 | 853 | false 854 | 855 | 856 | false 857 | 858 | 859 | true 860 | 861 | 862 | 863 | 864 | 865 | 866 | 867 | 868 | 869 | 870 | :/icons/lyric_hide:/icons/lyric_hide 871 | 872 | 873 | true 874 | 875 | 876 | 877 | 878 | 879 | 880 | 881 | 882 | 883 | 884 | 0 885 | 0 886 | 956 887 | 26 888 | 889 | 890 | 891 | 892 | 893 | 894 | LyricStreamWidget 895 | QWidget 896 |
lyricstreamwidget.h
897 | 1 898 |
899 | 900 | InteractiveButtonBase 901 | QPushButton 902 |
interactivebuttonbase.h
903 |
904 | 905 | ClickSlider 906 | QSlider 907 |
clickslider.h
908 |
909 | 910 | RoundedPixmapLabel 911 | QLabel 912 |
roundedpixmaplabel.h
913 |
914 | 915 | ItemSelectionListView 916 | QListView 917 |
itemselectionlistview.h
918 |
919 |
920 | 921 | 922 | 923 | 924 |
925 | -------------------------------------------------------------------------------- /order_player/roundedpixmaplabel.h: -------------------------------------------------------------------------------- 1 | #ifndef ROUNDEDLABEL_H 2 | #define ROUNDEDLABEL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class RoundedPixmapLabel : public QLabel 9 | { 10 | public: 11 | RoundedPixmapLabel(QWidget* parent = nullptr) : QLabel(parent) 12 | { 13 | } 14 | 15 | protected: 16 | void paintEvent(QPaintEvent *) override 17 | { 18 | const QPixmap* pixmap = this->pixmap(); 19 | if (!pixmap) 20 | return ; 21 | QPainter painter(this); 22 | QPainterPath path; 23 | path.addRoundedRect(rect(), 5, 5); 24 | painter.setClipPath(path); 25 | painter.setRenderHint(QPainter::SmoothPixmapTransform); 26 | painter.drawPixmap(rect(), *pixmap); 27 | } 28 | }; 29 | 30 | #endif // ROUNDEDLABEL_H 31 | -------------------------------------------------------------------------------- /order_player/songbeans.h: -------------------------------------------------------------------------------- 1 | #ifndef SONGBEANS_H 2 | #define SONGBEANS_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define JSON_VAL_LONG(json, x) static_cast(json.value(#x).toDouble()) 12 | #define JVAL_LONG(x) static_cast(json.value(#x).toDouble()) 13 | #define JVAL_INT(x) json.value(#x).toInt() 14 | #define JVAL_STR(x) json.value(#x).toString() 15 | #define snum(x) QString::number(x) 16 | #define cast(x,y) static_cast(y) 17 | 18 | enum MusicSource 19 | { 20 | UnknowMusic = -1, 21 | NeteaseCloudMusic = 0, 22 | QQMusic = 1 23 | }; 24 | 25 | struct Artist 26 | { 27 | qint64 id = 0; 28 | QString mid; 29 | QString name; 30 | QString faceUrl; 31 | char m_padding1[4]; 32 | 33 | static Artist fromJson(QJsonObject json) 34 | { 35 | Artist artist; 36 | artist.id = JVAL_LONG(id); 37 | if (json.contains("mid")) 38 | artist.mid = JVAL_STR(mid); 39 | artist.name = JVAL_STR(name); 40 | artist.faceUrl = JVAL_STR(img1v1Url); 41 | return artist; 42 | } 43 | 44 | static Artist fromQQMusicJson(QJsonObject json) 45 | { 46 | Artist artist; 47 | artist.id = JVAL_LONG(id); 48 | artist.mid = JVAL_STR(mid); 49 | artist.name = JVAL_STR(name); 50 | // artist.faceUrl = JVAL_STR(img1v1Url); 51 | return artist; 52 | } 53 | 54 | QJsonObject toJson() const 55 | { 56 | QJsonObject json; 57 | json.insert("id", id); 58 | json.insert("mid", mid); 59 | json.insert("name", name); 60 | json.insert("faceUrl", faceUrl); 61 | return json; 62 | } 63 | }; 64 | 65 | struct Album 66 | { 67 | qint64 id = 0; 68 | QString mid; 69 | QString name; 70 | int size = 0; 71 | int mark = 0; 72 | 73 | static Album fromJson(QJsonObject json) 74 | { 75 | Album album; 76 | album.id = JVAL_LONG(id); 77 | if (json.contains("mid")) 78 | album.mid = JVAL_STR(mid); 79 | album.name = JVAL_STR(name); 80 | album.size = JVAL_INT(size); 81 | album.mark = JVAL_INT(mark); 82 | return album; 83 | } 84 | 85 | static Album fromQQMusicJson(QJsonObject json) 86 | { 87 | Album album; 88 | album.id = JVAL_LONG(id); 89 | album.mid = JVAL_STR(mid); 90 | album.name = JVAL_STR(name); 91 | // album.size = JVAL_INT(size); 92 | // album.mark = JVAL_INT(mark); 93 | return album; 94 | } 95 | 96 | QJsonObject toJson() const 97 | { 98 | QJsonObject json; 99 | json.insert("id", id); 100 | json.insert("mid", mid); 101 | json.insert("name", name); 102 | json.insert("size", size); 103 | json.insert("mark", mark); 104 | return json; 105 | } 106 | }; 107 | 108 | struct Song 109 | { 110 | qint64 id = 0; 111 | QString mid; 112 | QString name; 113 | int duration = 0; 114 | int mark = 0; 115 | QList artists; 116 | char m_padding1[4]; 117 | Album album; 118 | QString artistNames; 119 | char m_padding2[4]; 120 | qint64 addTime; 121 | QString addBy; 122 | MusicSource source = NeteaseCloudMusic; 123 | 124 | static Song fromJson(QJsonObject json) 125 | { 126 | Song song; 127 | song.id = JVAL_LONG(id); 128 | song.mid = JVAL_STR(mid); 129 | song.name = JVAL_STR(name); 130 | QJsonArray array = json.value("artists").toArray(); 131 | QStringList artistNameList; 132 | foreach (QJsonValue val, array) 133 | { 134 | Artist artist = Artist::fromJson(val.toObject()); 135 | song.artists.append(artist); 136 | artistNameList.append(artist.name); 137 | } 138 | song.artistNames = artistNameList.join("/"); 139 | song.album = Album::fromJson(json.value("album").toObject()); 140 | song.duration = JVAL_INT(duration); // 毫秒 141 | song.mark = JVAL_INT(mark); 142 | if (json.contains("addTime")) 143 | song.addTime = JVAL_LONG(addTime); 144 | if (json.contains("addBy")) 145 | song.addBy = JVAL_STR(addBy); 146 | if (json.contains("source")) 147 | song.source = static_cast(JVAL_INT(source)); 148 | return song; 149 | } 150 | 151 | static Song fromQQMusicJson(QJsonObject json) 152 | { 153 | Song song; 154 | song.id = JVAL_LONG(id); 155 | if (json.contains("mid")) 156 | song.mid = JVAL_STR(mid); 157 | song.name = JVAL_STR(name); 158 | QJsonArray array = json.value("singer").toArray(); 159 | QStringList artistNameList; 160 | foreach (QJsonValue val, array) 161 | { 162 | Artist artist = Artist::fromQQMusicJson(val.toObject()); 163 | song.artists.append(artist); 164 | artistNameList.append(artist.name); 165 | } 166 | song.artistNames = artistNameList.join("/"); 167 | song.album = Album::fromQQMusicJson(json.value("album").toObject()); 168 | song.duration = JVAL_INT(interval) * 1000; // 秒数,转毫秒 169 | song.mark = JVAL_INT(mark); 170 | if (json.contains("addTime")) 171 | song.addTime = JVAL_LONG(addTime); 172 | if (json.contains("addBy")) 173 | song.addBy = JVAL_STR(addBy); 174 | song.source = QQMusic; 175 | return song; 176 | } 177 | 178 | static Song fromNeteaseShareJson(QJsonObject json) 179 | { 180 | Song song; 181 | song.id = JVAL_LONG(id); 182 | song.name = JVAL_STR(name); 183 | QJsonArray array = json.value("ar").toArray(); 184 | QStringList artistNameList; 185 | foreach (QJsonValue val, array) 186 | { 187 | Artist artist = Artist::fromJson(val.toObject()); 188 | song.artists.append(artist); 189 | artistNameList.append(artist.name); 190 | } 191 | song.artistNames = artistNameList.join("/"); 192 | song.album = Album::fromJson(json.value("al").toObject()); 193 | song.duration = JVAL_INT(dt); // 毫秒 194 | song.mark = JVAL_INT(mark); 195 | song.source = NeteaseCloudMusic; 196 | return song; 197 | } 198 | 199 | QJsonObject toJson() const 200 | { 201 | QJsonObject json; 202 | json.insert("id", id); 203 | json.insert("mid", mid); 204 | json.insert("name", name); 205 | json.insert("duration", duration); 206 | json.insert("mark", mark); 207 | QJsonArray array; 208 | foreach (Artist artist, artists) 209 | array.append(artist.toJson()); 210 | json.insert("artists", array); 211 | json.insert("album", album.toJson()); 212 | if (addTime) 213 | json.insert("addTime", addTime); 214 | if (!addBy.isEmpty()) 215 | json.insert("addBy", addBy); 216 | json.insert("source", static_cast(source)); 217 | return json; 218 | } 219 | 220 | bool isValid() const 221 | { 222 | return id; 223 | } 224 | 225 | bool operator==(const Song& song) const 226 | { 227 | return this->id == song.id; 228 | } 229 | 230 | bool operator!=(const Song& song) const 231 | { 232 | return this->id != song.id; 233 | } 234 | 235 | QString simpleString() const 236 | { 237 | return name + " - " + artistNames; 238 | } 239 | 240 | void setAddDesc(QString by) 241 | { 242 | this->addBy = by; 243 | this->addTime = QDateTime::currentSecsSinceEpoch(); 244 | } 245 | 246 | bool is(MusicSource source) const 247 | { 248 | return this->source == source; 249 | } 250 | }; 251 | 252 | struct PlayListCreator 253 | { 254 | QString nickname; 255 | QString signature; 256 | QString avatarUrl; 257 | }; 258 | 259 | struct PlayList 260 | { 261 | QString name; 262 | qint64 id; 263 | QString description; 264 | QStringList tags; 265 | int playCount; 266 | PlayListCreator creator; 267 | MusicSource source = NeteaseCloudMusic; 268 | }; 269 | 270 | typedef QList SongList; 271 | typedef QList PlayListList; 272 | 273 | #endif // SONGBEANS_H 274 | -------------------------------------------------------------------------------- /resource.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resource.rc -------------------------------------------------------------------------------- /resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | resources/icons/volume.png 4 | resources/icons/list_circle.png 5 | resources/icons/random_circle.png 6 | resources/icons/single_circle.png 7 | resources/icons/pause.png 8 | resources/icons/play.png 9 | resources/icons/search.png 10 | resources/icons/egg.png 11 | resources/icons/heart.png 12 | resources/icons/moon.png 13 | resources/icons/heart2.png 14 | resources/icons/candy.png 15 | resources/icons/mute.png 16 | resources/icons/order_list.png 17 | resources/icons/lyric_hide.png 18 | resources/icons/lyric_show.png 19 | resources/icons/sub_menu_arrow.png 20 | resources/icons/settings.png 21 | resources/icons/favorite.png 22 | resources/icons/history.png 23 | resources/icons/playlist.png 24 | resources/icons/random.png 25 | resources/icons/order.png 26 | resources/icons/next_song.png 27 | 28 | 29 | resources/musics/netease-cloud-music.png 30 | resources/musics/QQ-music.png 31 | 32 | 33 | resources/snow/neve_music.png 34 | resources/snow/snow1.png 35 | resources/snow/snow2.png 36 | resources/snow/snow3.png 37 | resources/snow/snow4.png 38 | resources/snow/snow5.png 39 | resources/snow/snowman.png 40 | 41 | 42 | resources/bg/bg.png 43 | 44 | 45 | -------------------------------------------------------------------------------- /resources/bg/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/bg/bg.png -------------------------------------------------------------------------------- /resources/icons/candy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/candy.png -------------------------------------------------------------------------------- /resources/icons/egg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/egg.png -------------------------------------------------------------------------------- /resources/icons/favorite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/favorite.png -------------------------------------------------------------------------------- /resources/icons/favorite0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/favorite0.png -------------------------------------------------------------------------------- /resources/icons/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/heart.png -------------------------------------------------------------------------------- /resources/icons/heart2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/heart2.png -------------------------------------------------------------------------------- /resources/icons/history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/history.png -------------------------------------------------------------------------------- /resources/icons/list_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/list_circle.png -------------------------------------------------------------------------------- /resources/icons/lyric_hide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/lyric_hide.png -------------------------------------------------------------------------------- /resources/icons/lyric_show.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/lyric_show.png -------------------------------------------------------------------------------- /resources/icons/moon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/moon.png -------------------------------------------------------------------------------- /resources/icons/mute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/mute.png -------------------------------------------------------------------------------- /resources/icons/next_song.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/next_song.png -------------------------------------------------------------------------------- /resources/icons/order.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/order.png -------------------------------------------------------------------------------- /resources/icons/order_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/order_list.png -------------------------------------------------------------------------------- /resources/icons/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/pause.png -------------------------------------------------------------------------------- /resources/icons/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/play.png -------------------------------------------------------------------------------- /resources/icons/playlist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/playlist.png -------------------------------------------------------------------------------- /resources/icons/random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/random.png -------------------------------------------------------------------------------- /resources/icons/random_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/random_circle.png -------------------------------------------------------------------------------- /resources/icons/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/search.png -------------------------------------------------------------------------------- /resources/icons/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/settings.png -------------------------------------------------------------------------------- /resources/icons/single_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/single_circle.png -------------------------------------------------------------------------------- /resources/icons/sub_menu_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/sub_menu_arrow.png -------------------------------------------------------------------------------- /resources/icons/volume.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/icons/volume.png -------------------------------------------------------------------------------- /resources/musics/QQ-music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/musics/QQ-music.png -------------------------------------------------------------------------------- /resources/musics/netease-cloud-music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/musics/netease-cloud-music.png -------------------------------------------------------------------------------- /resources/snow/neve_music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/snow/neve_music.png -------------------------------------------------------------------------------- /resources/snow/snow1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/snow/snow1.png -------------------------------------------------------------------------------- /resources/snow/snow2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/snow/snow2.png -------------------------------------------------------------------------------- /resources/snow/snow3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/snow/snow3.png -------------------------------------------------------------------------------- /resources/snow/snow4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/snow/snow4.png -------------------------------------------------------------------------------- /resources/snow/snow5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/snow/snow5.png -------------------------------------------------------------------------------- /resources/snow/snowman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/resources/snow/snowman.png -------------------------------------------------------------------------------- /screenshots/custom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/screenshots/custom.png -------------------------------------------------------------------------------- /screenshots/lists.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/screenshots/lists.png -------------------------------------------------------------------------------- /screenshots/music0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/screenshots/music0.png -------------------------------------------------------------------------------- /screenshots/music1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/screenshots/music1.png -------------------------------------------------------------------------------- /screenshots/music2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/screenshots/music2.png -------------------------------------------------------------------------------- /screenshots/music3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/screenshots/music3.png -------------------------------------------------------------------------------- /screenshots/music4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/screenshots/music4.png -------------------------------------------------------------------------------- /screenshots/music5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/screenshots/music5.png -------------------------------------------------------------------------------- /screenshots/music6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/screenshots/music6.png -------------------------------------------------------------------------------- /screenshots/switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iwxyi/NeveMusic/4e2f0c8d9b491b24f8401c10d4c2a5271d0933d6/screenshots/switch.png --------------------------------------------------------------------------------