thememanager.cpp 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186
  1. #include "thememanager.h"
  2. #include <QApplication>
  3. #include <QFile>
  4. #include <QJsonDocument>
  5. #include <QMetaEnum>
  6. #include <cmath>
  7. static const QByteArray defaultConfig = R"Raw(
  8. {
  9. "color_mode": {
  10. "light": {
  11. },
  12. "dark": {
  13. }
  14. },
  15. "size_mode": {
  16. "small": {
  17. "font_base": "12px",
  18. "padding_base": "4px",
  19. "radius_base": "2px",
  20. "control_height": "20px"
  21. },
  22. "medium": {
  23. "font_base": "14px",
  24. "padding_base": "8px",
  25. "radius_base": "4px",
  26. "control_height": "28px"
  27. },
  28. "large": {
  29. "font_base": "16px",
  30. "padding_base": "12px",
  31. "radius_base": "6px",
  32. "control_height": "36px"
  33. }
  34. }
  35. }
  36. )Raw";
  37. static const QByteArray defaultQss = R"Raw(
  38. /********************* 基础样式 *********************/
  39. QWidget {
  40. font-family: "Segoe UI", "Microsoft YaHei";
  41. font-size: @font_base;
  42. color: @colorText;
  43. background-color: @colorBgContainer;
  44. }
  45. * {
  46. font-family: "Segoe UI", "Microsoft YaHei";
  47. font-size: @font_base;
  48. color: @colorText;
  49. background-color: @colorBgContainer;
  50. selection-background-color: @colorPrimaryBg;
  51. selection-color: @colorPrimaryText;
  52. }
  53. /********************* 窗口栏 *********************/
  54. QWK--WindowBar[bar-active=true] {
  55. background-color: @colorPrimary;
  56. }
  57. QWK--WindowBar[bar-active=false] {
  58. background-color: @colorPrimaryBgHover;
  59. }
  60. QWK--WindowBar>QLabel#win-title-label {
  61. padding: 0;
  62. border: none;
  63. color: @colorText;
  64. background-color: transparent;
  65. min-height: 28px;
  66. }
  67. /* 系统按钮 */
  68. QWK--WindowBar>QAbstractButton[system-button=true] {
  69. qproperty-iconSize: 12px 12px;
  70. min-width: 40px;
  71. border: none;
  72. padding: 0;
  73. background-color: transparent;
  74. }
  75. QWK--WindowBar>QAbstractButton#settings-button {
  76. qproperty-iconNormal: url(":/window-bar/more-line.svg");
  77. qproperty-iconChecked: url(":/window-bar/more-line.svg");
  78. qproperty-iconSize: 15px 15px;
  79. }
  80. QWK--WindowBar>QAbstractButton#settings-button:hover,
  81. QWK--WindowBar>QAbstractButton#settings-button:pressed {
  82. background-color: @colorPrimaryBgHover;
  83. }
  84. QWK--WindowBar>QAbstractButton#pin-button {
  85. qproperty-iconNormal: url(":/window-bar/pin.svg");
  86. qproperty-iconChecked: url(":/window-bar/pin-fill.svg");
  87. qproperty-iconSize: 15px 15px;
  88. }
  89. QWK--WindowBar>QAbstractButton#pin-button:hover,
  90. QWK--WindowBar>QAbstractButton#pin-button:pressed {
  91. background-color: @colorPrimaryBgHover;
  92. }
  93. QWK--WindowBar>QAbstractButton#min-button {
  94. qproperty-iconNormal: url(":/window-bar/minimize.svg");
  95. }
  96. QWK--WindowBar>QAbstractButton#min-button:hover,
  97. QWK--WindowBar>QAbstractButton#min-button:pressed {
  98. background-color: @colorPrimaryBgHover;
  99. }
  100. QWK--WindowBar>QAbstractButton#max-button {
  101. qproperty-iconNormal: url(":/window-bar/maximize.svg");
  102. qproperty-iconChecked: url(":/window-bar/restore.svg");
  103. }
  104. QWK--WindowBar>QAbstractButton#max-button:hover,
  105. QWK--WindowBar>QAbstractButton#max-button:pressed {
  106. background-color: @colorPrimaryBgHover;
  107. }
  108. QWK--WindowBar>QAbstractButton#close-button {
  109. qproperty-iconNormal: url(":/window-bar/close.svg");
  110. }
  111. QWK--WindowBar>QAbstractButton#close-button:hover,
  112. QWK--WindowBar>QAbstractButton#close-button:pressed {
  113. background-color: @colorError;
  114. }
  115. QWK--WindowBar>QAbstractButton#icon-button {
  116. qproperty-iconNormal: url(":/app/example.png");
  117. qproperty-iconSize: 18px 18px;
  118. min-width: 40px;
  119. border: none;
  120. padding: 0;
  121. background-color: transparent;
  122. }
  123. /********************* 通用控件 *********************/
  124. /* 按钮 */
  125. QPushButton {
  126. background-color: @colorPrimary;
  127. color: @colorWhite;
  128. border: 1px solid @colorPrimaryBorder;
  129. border-radius: @radius_base;
  130. padding: @padding_base;
  131. min-height: @control_height;
  132. min-width: 80px;
  133. }
  134. QPushButton:hover {
  135. background-color: @colorPrimaryHover;
  136. border-color: @colorPrimaryBorderHover;
  137. }
  138. QPushButton:pressed {
  139. background-color: @colorPrimaryActive;
  140. border-color: @colorPrimaryBorder;
  141. }
  142. QPushButton:disabled {
  143. background-color: @colorFillQuaternary;
  144. border-color: @colorBorderSecondary;
  145. color: @colorTextQuaternary;
  146. }
  147. /* 标签 */
  148. QLabel {
  149. background-color: transparent;
  150. padding: @padding_base;
  151. color: @colorText;
  152. }
  153. /* 输入控件 */
  154. QLineEdit, QTextEdit, QPlainTextEdit, QComboBox, QSpinBox, QDateTimeEdit {
  155. background-color: @colorBgContainer;
  156. border: 1px solid @colorBorder;
  157. border-radius: @radius_base;
  158. padding: @padding_base;
  159. min-height: @control_height;
  160. color: @colorText;
  161. }
  162. QLineEdit:hover, QTextEdit:hover, QPlainTextEdit:hover,
  163. QComboBox:hover, QSpinBox:hover, QDateTimeEdit:hover {
  164. border-color: @colorPrimaryHover;
  165. background-color: @colorBgContainer;
  166. }
  167. QLineEdit:focus, QTextEdit:focus, QPlainTextEdit:focus,
  168. QComboBox:focus, QSpinBox:focus, QDateTimeEdit:focus {
  169. border-color: @colorPrimary;
  170. background-color: @colorBgContainer;
  171. }
  172. QComboBox::drop-down {
  173. subcontrol-origin: padding;
  174. subcontrol-position: center right;
  175. width: 20px;
  176. border-left: 1px solid @colorBorder;
  177. }
  178. QComboBox QAbstractItemView {
  179. background: @colorBgContainer;
  180. border: 1px solid @colorBorder;
  181. selection-background-color: @colorPrimaryBg;
  182. selection-color: @colorPrimaryText;
  183. }
  184. /* 选择控件 */
  185. QCheckBox, QRadioButton {
  186. background-color: transparent;
  187. color: @colorText;
  188. }
  189. QCheckBox::indicator, QRadioButton::indicator {
  190. width: 16px;
  191. height: 16px;
  192. }
  193. QCheckBox::indicator {
  194. border: 1px solid @colorBorder;
  195. border-radius: @radius_small;
  196. background: @colorBgContainer;
  197. }
  198. QCheckBox::indicator:checked {
  199. background-color: @colorPrimary;
  200. border-color: @colorPrimary;
  201. image: url(":/theme/checked.svg");
  202. }
  203. QCheckBox:disabled {
  204. color: @colorTextQuaternary;
  205. }
  206. QRadioButton::indicator {
  207. border: 1px solid @colorBorder;
  208. border-radius: 8px;
  209. background: @colorBgContainer;
  210. }
  211. QRadioButton::indicator:checked {
  212. background-color: @colorPrimary;
  213. border-color: @colorPrimary;
  214. image: url(":/theme/radio-checked.svg");
  215. }
  216. QRadioButton:disabled {
  217. color: @colorTextQuaternary;
  218. }
  219. /********************* 复杂控件 *********************/
  220. /* 选项卡 */
  221. QTabWidget::pane {
  222. border: 1px solid @colorBorder;
  223. border-radius: @radius_base;
  224. }
  225. QTabBar::tab {
  226. background: @colorBgContainer;
  227. border: 1px solid transparent;
  228. padding: @padding_base;
  229. margin-right: 4px;
  230. color: @colorText;
  231. }
  232. QTabBar::tab:selected {
  233. background: @colorPrimaryBg;
  234. border-bottom: 2px solid @colorPrimary;
  235. color: @colorPrimaryText;
  236. }
  237. QTabBar::tab:hover {
  238. background: @colorPrimaryBgHover;
  239. }
  240. /* 滚动条 */
  241. /* 滚动条整体 */
  242. QScrollBar:vertical {
  243. background: @colorFillQuaternary; /* 滚动条轨道背景 */
  244. width: 10px; /* 垂直滚动条宽度 */
  245. margin: 10px 0 10px 0; /* 上下留白 */
  246. }
  247. QScrollBar:horizontal {
  248. background: @colorFillQuaternary; /* 滚动条轨道背景 */
  249. height: 10px; /* 水平滚动条高度 */
  250. margin: 0 10px 0 10px; /* 左右留白 */
  251. }
  252. /* 滑块(Handle) */
  253. QScrollBar::handle:vertical {
  254. background: #eaeaea; /* 滑块颜色 */
  255. min-height: 30px; /* 垂直滑块最小高度(重要!) */
  256. border-radius: 5px; /* 可选圆角 */
  257. }
  258. QScrollBar::handle:horizontal {
  259. background: #eaeaea; /* 滑块颜色 */
  260. min-width: 30px; /* 水平滑块最小宽度(重要!) */
  261. border-radius: 5px; /* 可选圆角 */
  262. }
  263. /* 滑块悬停/按下效果 */
  264. QScrollBar::handle:vertical:hover,
  265. QScrollBar::handle:horizontal:hover {
  266. background: #b0b0b0; /* 悬停颜色 */
  267. }
  268. QScrollBar::handle:vertical:pressed,
  269. QScrollBar::handle:horizontal:pressed {
  270. background: #b0b0b0; /* 按下颜色 */
  271. }
  272. /* 上下/左右箭头按钮(隐藏箭头) */
  273. QScrollBar::add-line:vertical,
  274. QScrollBar::sub-line:vertical,
  275. QScrollBar::add-line:horizontal,
  276. QScrollBar::sub-line:horizontal {
  277. background: none; /* 隐藏箭头按钮背景 */
  278. border: none; /* 移除边框 */
  279. height: 0; /* 垂直箭头高度设为0 */
  280. width: 0; /* 水平箭头宽度设为0 */
  281. }
  282. /* 滚动条边缘的空白区域(Add-Page/Sub-Page) */
  283. QScrollBar::add-page:vertical,
  284. QScrollBar::sub-page:vertical,
  285. QScrollBar::add-page:horizontal,
  286. QScrollBar::sub-page:horizontal {
  287. background: none; /* 移除默认灰色背景 */
  288. }
  289. /* 树形视图 */
  290. QTreeView {
  291. show-decoration-selected: 1;
  292. alternate-background-color: @colorBgContainer;
  293. }
  294. QTreeView::item:hover {
  295. background: @colorPrimaryBgHover;
  296. }
  297. QTreeView::item:selected {
  298. background: @colorPrimaryBg;
  299. color: @colorPrimaryText;
  300. }
  301. /********************* 列表视图 *********************/
  302. QListView {
  303. outline: none;
  304. background-color: @colorBgContainer;
  305. alternate-background-color: @colorFillQuaternary;
  306. border: 1px solid @colorBorder;
  307. border-radius: @radius_base;
  308. padding: 2px;
  309. }
  310. QListView::item {
  311. border-radius: @radius_small;
  312. padding: @padding_base;
  313. margin: 1px;
  314. }
  315. QListView::item:hover {
  316. background: @colorPrimaryBgHover;
  317. }
  318. QListView::item:selected {
  319. background: @colorPrimaryBg;
  320. color: @colorPrimaryText;
  321. }
  322. QListView::item:selected:active {
  323. background: @colorPrimaryBg;
  324. color: @colorPrimaryText;
  325. }
  326. QListView::item:selected:!active {
  327. background: @colorFillSecondary;
  328. color: @colorTextSecondary;
  329. }
  330. /********************* 分组框 *********************/
  331. QGroupBox {
  332. border: 1px solid @colorBorder;
  333. border-radius: @radius_base;
  334. margin-top: 16px;
  335. padding-top: 20px;
  336. }
  337. QGroupBox::title {
  338. subcontrol-origin: margin;
  339. subcontrol-position: top left;
  340. padding: 0 4px;
  341. color: @colorTextSecondary;
  342. left: 12px;
  343. }
  344. QGroupBox {
  345. background-color: transparent;
  346. border: 1px solid @colorBorder;
  347. border-radius: @radius_base;
  348. margin-top: 1ex;
  349. }
  350. /********************* 滑块控件 *********************/
  351. QSlider::groove:horizontal {
  352. height: 4px;
  353. background: @colorFillQuaternary;
  354. border-radius: 2px;
  355. }
  356. QSlider::handle:horizontal {
  357. width: 16px;
  358. margin: -6px 0;
  359. background: @colorPrimary;
  360. border-radius: 8px;
  361. }
  362. QSlider::add-page:horizontal {
  363. background: @colorFillQuaternary;
  364. }
  365. QSlider::sub-page:horizontal {
  366. background: @colorPrimary;
  367. }
  368. /********************* 进度条 *********************/
  369. QProgressBar {
  370. border: 1px solid @colorBorder;
  371. border-radius: @radius_base;
  372. background: @colorFillQuaternary;
  373. text-align: center;
  374. color: @colorText;
  375. padding: 1px;
  376. min-height: 10px;
  377. }
  378. QProgressBar::chunk {
  379. background: @colorPrimary;
  380. border-radius: calc(@radius_base - 1px);
  381. }
  382. /********************* 菜单控件 *********************/
  383. QMenu {
  384. background: @colorBgContainer;
  385. border: 1px solid @colorBorder;
  386. padding: 4px;
  387. }
  388. QMenu::item {
  389. padding: 6px 24px;
  390. margin: 2px;
  391. border-radius: @radius_small;
  392. }
  393. QMenu::item:selected {
  394. background: @colorPrimaryBg;
  395. color: @colorPrimaryText;
  396. }
  397. QMenu::separator {
  398. height: 1px;
  399. background: @colorBorder;
  400. }
  401. /********************* 表格控件 *********************/
  402. QHeaderView::section {
  403. background: @colorBgContainer;
  404. padding: 4px;
  405. border: 1px solid @colorBorder;
  406. }
  407. QTableView QTableCornerButton::section {
  408. background: @colorBgContainer;
  409. border: 1px solid @colorBorder;
  410. }
  411. /********************* 工具栏 *********************/
  412. QToolBar {
  413. background: @colorBgContainer;
  414. border-bottom: 1px solid @colorBorder;
  415. spacing: 4px;
  416. padding: 4px;
  417. }
  418. QToolBar::separator {
  419. width: 1px;
  420. background: @colorBorder;
  421. margin: 0 4px;
  422. }
  423. /********************* 对话框 *********************/
  424. QDialog {
  425. background: @colorBgContainer;
  426. border: 1px solid @colorBorder;
  427. border-radius: @radius_base;
  428. }
  429. QMessageBox QLabel#qt_msgbox_label {
  430. color: @colorText;
  431. padding: 12px;
  432. }
  433. QMessageBox QPushButton {
  434. min-width: 80px;
  435. margin: 4px;
  436. }
  437. /********************* 工具提示 *********************/
  438. QToolTip {
  439. color: @colorText;
  440. background-color: @colorBgContainer;
  441. border: 1px solid @colorBorder;
  442. border-radius: @radius_small;
  443. padding: 4px 8px;
  444. }
  445. /********************* 特殊状态 *********************/
  446. /* 错误状态 */
  447. .error-border {
  448. border: 1px solid @colorError !important;
  449. }
  450. .error-text {
  451. color: @colorError !important;
  452. }
  453. /* 禁用状态 */
  454. .disabled-text {
  455. color: @colorTextQuaternary;
  456. }
  457. .disabled-bg {
  458. background-color: @colorFillQuaternary;
  459. }
  460. /* 链接样式 */
  461. QLabel[link="true"] {
  462. color: @colorLink;
  463. }
  464. QLabel[link="true"]:hover {
  465. color: @colorLinkHover;
  466. }
  467. /********************* 动态尺寸 *********************/
  468. .small {
  469. font-size: 12px;
  470. padding: 4px;
  471. qproperty-iconSize: 16px;
  472. }
  473. .medium {
  474. font-size: 14px;
  475. padding: 8px;
  476. qproperty-iconSize: 20px;
  477. }
  478. .large {
  479. font-size: 16px;
  480. padding: 12px;
  481. qproperty-iconSize: 24px;
  482. }
  483. /********************* 工具按钮 *********************/
  484. QToolButton {
  485. padding: 4px;
  486. border-radius: @radius_small;
  487. background: transparent;
  488. }
  489. QToolButton:hover {
  490. background: @colorPrimaryBgHover;
  491. }
  492. QToolButton:pressed {
  493. background: @colorPrimaryBg;
  494. }
  495. /********************* 分割线 *********************/
  496. QSplitter::handle {
  497. background: @colorBorder;
  498. margin: 2px;
  499. }
  500. QSplitter::handle:hover {
  501. background: @colorPrimary;
  502. }
  503. /********************* 状态栏 *********************/
  504. QStatusBar {
  505. background-color: @colorBgContainer;
  506. border-top: 1px solid @colorBorder;
  507. color: @colorText;
  508. min-height: 24px;
  509. padding: 2px 4px;
  510. }
  511. QStatusBar::item {
  512. border-right: 1px solid @colorBorder;
  513. margin: 0;
  514. }
  515. /********************* 状态类 *********************/
  516. .active {
  517. background-color: @colorPrimaryActive;
  518. color: @colorPrimaryTextActive;
  519. }
  520. .hover {
  521. background-color: @colorPrimaryBgHover;
  522. color: @colorPrimaryTextHover;
  523. }
  524. )Raw";
  525. namespace {
  526. const int hueStep = 2; // 色相阶梯
  527. const double saturationStep = 0.16; // 饱和度阶梯,浅色部分
  528. const double saturationStep2 = 0.05; // 饱和度阶梯,深色部分
  529. const double brightnessStep1 = 0.05; // 亮度阶梯,浅色部分
  530. const double brightnessStep2 = 0.15; // 亮度阶梯,深色部分
  531. const int lightColorCount = 5; // 浅色数量,主色上
  532. const int darkColorCount = 4; // 深色数量,主色下
  533. // 暗色主题颜色映射关系表
  534. const QVector<int> darkColorMap = {7, 6, 5, 5, 5, 5, 4, 3, 2, 1};
  535. const QVector<int> darkMixRatios = {15, 25, 30, 45, 65, 85, 90, 95, 97, 98};
  536. } // namespace
  537. ThemeManager &ThemeManager::instance()
  538. {
  539. static ThemeManager instance;
  540. return instance;
  541. }
  542. ThemeManager::ThemeManager(QObject *parent)
  543. : QObject(parent)
  544. {
  545. token.colorPrimary = QColor("#1677ff");
  546. token.colorSuccess = QColor("#52c41a");
  547. token.colorWarning = QColor("#faad14");
  548. token.colorError = QColor("#ff4d4f");
  549. token.colorInfo = QColor("#1677ff");
  550. token.colorLink = QColor("");
  551. token.colorTextBase = QColor("");
  552. token.colorBgBase = QColor("");
  553. token.fontFamily = QFont();
  554. // Line
  555. token.lineWidth = 1;
  556. token.lineType = "solid";
  557. // Radius
  558. token.borderRadius = 6;
  559. // Size
  560. token.sizeUnit = 4;
  561. token.sizeStep = 4;
  562. token.sizePopupArrow = 16;
  563. token.controlHeight = 32;
  564. QJsonDocument doc = QJsonDocument::fromJson(defaultConfig);
  565. loadConfig(doc.object());
  566. }
  567. void ThemeManager::loadConfig(const QJsonObject &object)
  568. {
  569. m_colorConfig = object.value("color_mode").toObject();
  570. m_sizeConfig = object.value("size_mode").toObject();
  571. generateDerivedColors();
  572. }
  573. void ThemeManager::loadConfig(const QString &configPath)
  574. {
  575. QFile configFile(configPath);
  576. if (!configFile.open(QIODevice::ReadOnly))
  577. return;
  578. QJsonDocument doc = QJsonDocument::fromJson(configFile.readAll());
  579. if (doc.isNull() || !doc.isObject()) {
  580. qWarning() << "Invalid JSON in config file:" << configPath;
  581. return;
  582. }
  583. loadConfig(doc.object());
  584. }
  585. void ThemeManager::applyTheme()
  586. {
  587. QString qss = loadQssTemplate();
  588. QString processed = processQss(qss);
  589. // qDebug().noquote() << "Processed QSS:" << processed; // 输出生成的 QSS
  590. qApp->setStyleSheet(processed);
  591. emit themeChanged();
  592. }
  593. QColor ThemeManager::color(const QString &name) const
  594. {
  595. QMetaEnum metaEnm = QMetaEnum::fromType<ColorToken>();
  596. bool isok = false;
  597. int value = metaEnm.keyToValue(name.toUtf8().data(), &isok);
  598. if (isok) {
  599. return m_colorTokens.value((ColorToken) value, QColor(Qt::black));
  600. }
  601. qDebug() << "no find " << name << m_colorTokens;
  602. return QColor(Qt::black);
  603. }
  604. QColor ThemeManager::color(ColorToken token) const
  605. {
  606. return m_colorTokens.value(token, QColor(Qt::black));
  607. }
  608. void ThemeManager::setPrimaryColor(const QColor &color)
  609. {
  610. if (token.colorPrimary != color && color.isValid()) {
  611. token.colorPrimary = color;
  612. generateDerivedColors();
  613. applyTheme();
  614. }
  615. }
  616. void ThemeManager::toggleThemeMode()
  617. {
  618. m_themeMode = (m_themeMode == Light) ? Dark : Light;
  619. generateDerivedColors();
  620. applyTheme();
  621. }
  622. ThemeManager::ThemeMode ThemeManager::themeMode() const
  623. {
  624. return m_themeMode;
  625. }
  626. void ThemeManager::setThemeMode(ThemeMode themeMode)
  627. {
  628. if (m_themeMode != themeMode) {
  629. m_themeMode = themeMode;
  630. generateDerivedColors();
  631. }
  632. applyTheme();
  633. }
  634. ThemeManager::SizeMode ThemeManager::sizeMode() const
  635. {
  636. return m_sizeMode;
  637. }
  638. void ThemeManager::setSizeMode(SizeMode mode)
  639. {
  640. if (m_sizeMode != mode) {
  641. m_sizeMode = mode;
  642. applyTheme();
  643. }
  644. }
  645. void ThemeManager::generateDerivedColors()
  646. {
  647. m_colors.clear();
  648. auto generateColorPalettes = [this](const QColor baseColor) {
  649. if (!baseColor.isValid()) {
  650. return QHash<int, QColor>();
  651. }
  652. const QVector<QColor> colors = generateAntdPalette(baseColor);
  653. return QHash<int, QColor>{
  654. {1, colors[0]}, //
  655. {2, colors[1]}, //
  656. {3, colors[2]}, //
  657. {4, colors[3]}, //
  658. {5, colors[4]}, //
  659. {6, colors[5]}, //
  660. {7, colors[6]}, //
  661. {8, colors[4]}, //
  662. {9, colors[5]}, //
  663. {10, colors[6]}, //
  664. //{8: colors[7]},
  665. //{9: colors[8]},
  666. //{10: colors[9]};
  667. };
  668. };
  669. QString themeKey = (m_themeMode == Light) ? "light" : "dark";
  670. QJsonObject themeColors = m_colorConfig.value(themeKey).toObject();
  671. // 加载基础颜色
  672. for (auto it = themeColors.begin(); it != themeColors.end(); ++it) {
  673. QColor color(it.value().toString());
  674. if (color.isValid()) {
  675. m_colors[it.key()] = color;
  676. }
  677. }
  678. // 创建 类似 antd 的颜色配置
  679. const auto primaryColors = generateColorPalettes(token.colorPrimary);
  680. const auto successColors = generateColorPalettes(token.colorSuccess);
  681. const auto warningColors = generateColorPalettes(token.colorWarning);
  682. const auto errorColors = generateColorPalettes(token.colorError);
  683. const auto infoColors = generateColorPalettes(token.colorInfo);
  684. generateNeutralColorPalettes(token.colorBgBase, token.colorTextBase);
  685. // Color Link
  686. auto colorLink = token.colorLink;
  687. if (!token.colorLink.isValid()) {
  688. colorLink = token.colorInfo;
  689. }
  690. const auto linkColors = generateColorPalettes(colorLink);
  691. const auto colorErrorBgFilledHover = mixColor(errorColors[1], errorColors[3], 50);
  692. m_colorTokens[ColorToken::colorPrimaryBg] = primaryColors[1];
  693. m_colorTokens[ColorToken::colorPrimaryBgHover] = primaryColors[2];
  694. m_colorTokens[ColorToken::colorPrimaryBorder] = primaryColors[3];
  695. m_colorTokens[ColorToken::colorPrimaryBorderHover] = primaryColors[4];
  696. m_colorTokens[ColorToken::colorPrimaryHover] = primaryColors[5];
  697. m_colorTokens[ColorToken::colorPrimary] = primaryColors[6];
  698. m_colorTokens[ColorToken::colorPrimaryActive] = primaryColors[7];
  699. m_colorTokens[ColorToken::colorPrimaryTextHover] = primaryColors[8];
  700. m_colorTokens[ColorToken::colorPrimaryText] = primaryColors[9];
  701. m_colorTokens[ColorToken::colorPrimaryTextActive] = primaryColors[10];
  702. m_colorTokens[ColorToken::colorSuccessBg] = successColors[1];
  703. m_colorTokens[ColorToken::colorSuccessBgHover] = successColors[2];
  704. m_colorTokens[ColorToken::colorSuccessBorder] = successColors[3];
  705. m_colorTokens[ColorToken::colorSuccessBorderHover] = successColors[4];
  706. m_colorTokens[ColorToken::colorSuccessHover] = successColors[4];
  707. m_colorTokens[ColorToken::colorSuccess] = successColors[6];
  708. m_colorTokens[ColorToken::colorSuccessActive] = successColors[7];
  709. m_colorTokens[ColorToken::colorSuccessTextHover] = successColors[8];
  710. m_colorTokens[ColorToken::colorSuccessText] = successColors[9];
  711. m_colorTokens[ColorToken::colorSuccessTextActive] = successColors[10];
  712. m_colorTokens[ColorToken::colorErrorBg] = errorColors[1];
  713. m_colorTokens[ColorToken::colorErrorBgHover] = errorColors[2];
  714. m_colorTokens[ColorToken::colorErrorBgFilledHover] = colorErrorBgFilledHover;
  715. m_colorTokens[ColorToken::colorErrorBgActive] = errorColors[3];
  716. m_colorTokens[ColorToken::colorErrorBorder] = errorColors[3];
  717. m_colorTokens[ColorToken::colorErrorBorderHover] = errorColors[4];
  718. m_colorTokens[ColorToken::colorErrorHover] = errorColors[5];
  719. m_colorTokens[ColorToken::colorError] = errorColors[6];
  720. m_colorTokens[ColorToken::colorErrorActive] = errorColors[7];
  721. m_colorTokens[ColorToken::colorErrorTextHover] = errorColors[8];
  722. m_colorTokens[ColorToken::colorErrorText] = errorColors[9];
  723. m_colorTokens[ColorToken::colorErrorTextActive] = errorColors[10];
  724. m_colorTokens[ColorToken::colorWarningBg] = warningColors[1];
  725. m_colorTokens[ColorToken::colorWarningBgHover] = warningColors[2];
  726. m_colorTokens[ColorToken::colorWarningBorder] = warningColors[3];
  727. m_colorTokens[ColorToken::colorWarningBorderHover] = warningColors[4];
  728. m_colorTokens[ColorToken::colorWarningHover] = warningColors[4];
  729. m_colorTokens[ColorToken::colorWarning] = warningColors[6];
  730. m_colorTokens[ColorToken::colorWarningActive] = warningColors[7];
  731. m_colorTokens[ColorToken::colorWarningTextHover] = warningColors[8];
  732. m_colorTokens[ColorToken::colorWarningText] = warningColors[9];
  733. m_colorTokens[ColorToken::colorWarningTextActive] = warningColors[10];
  734. m_colorTokens[ColorToken::colorInfoBg] = infoColors[1];
  735. m_colorTokens[ColorToken::colorInfoBgHover] = infoColors[2];
  736. m_colorTokens[ColorToken::colorInfoBorder] = infoColors[3];
  737. m_colorTokens[ColorToken::colorInfoBorderHover] = infoColors[4];
  738. m_colorTokens[ColorToken::colorInfoHover] = infoColors[4];
  739. m_colorTokens[ColorToken::colorInfo] = infoColors[6];
  740. m_colorTokens[ColorToken::colorInfoActive] = infoColors[7];
  741. m_colorTokens[ColorToken::colorInfoTextHover] = infoColors[8];
  742. m_colorTokens[ColorToken::colorInfoText] = infoColors[9];
  743. m_colorTokens[ColorToken::colorInfoTextActive] = infoColors[10];
  744. m_colorTokens[ColorToken::colorLinkHover] = linkColors[4];
  745. m_colorTokens[ColorToken::colorLink] = linkColors[6];
  746. m_colorTokens[ColorToken::colorLinkActive] = linkColors[7];
  747. m_colorTokens[ColorToken::colorWhite] = "#fff";
  748. // init m_color
  749. QMetaEnum metaEnum = QMetaEnum::fromType<ColorToken>();
  750. for (auto it = m_colorTokens.constBegin(); it != m_colorTokens.constEnd(); ++it) {
  751. const char *enumKey = metaEnum.valueToKey(static_cast<int>(it.key()));
  752. if (enumKey) {
  753. m_colors.insert(QString(enumKey), it.value());
  754. } else {
  755. qWarning() << "Invalid ColorToken value:" << static_cast<int>(it.key());
  756. }
  757. }
  758. }
  759. QString ThemeManager::loadQssTemplate() const
  760. {
  761. return QString::fromUtf8(defaultQss);
  762. }
  763. QString ThemeManager::processQss(const QString &qss) const
  764. {
  765. QString processed = qss;
  766. // 第一步:替换尺寸变量(无依赖关系)
  767. QString sizeKey;
  768. switch (m_sizeMode) {
  769. case Small:
  770. sizeKey = "small";
  771. break;
  772. case Medium:
  773. sizeKey = "medium";
  774. break;
  775. case Large:
  776. sizeKey = "large";
  777. break;
  778. }
  779. QJsonObject sizeValues = m_sizeConfig.value(sizeKey).toObject();
  780. for (auto it = sizeValues.begin(); it != sizeValues.end(); ++it) {
  781. processed.replace("@" + it.key(), it.value().toString());
  782. }
  783. // 第二步:使用正则表达式替换颜色变量(处理依赖关系)
  784. static const QRegularExpression varRegex("@([a-zA-Z0-9_]+)");
  785. QRegularExpressionMatchIterator it = varRegex.globalMatch(processed);
  786. QSet<QString> replacedVars;
  787. while (it.hasNext()) {
  788. QRegularExpressionMatch match = it.next();
  789. QString varName = match.captured(1);
  790. if (replacedVars.contains(varName))
  791. continue;
  792. if (m_colors.contains(varName)) {
  793. QColor color = m_colors[varName];
  794. QString colorStr = color.alpha() == 255 ? color.name()
  795. : QString("rgba(%1, %2, %3, %4)")
  796. .arg(color.red())
  797. .arg(color.green())
  798. .arg(color.blue())
  799. .arg(color.alphaF());
  800. // 使用单词边界确保精确匹配
  801. processed.replace(QRegularExpression(QString("@%1\\b").arg(varName)), colorStr);
  802. replacedVars.insert(varName);
  803. }
  804. }
  805. return processed;
  806. }
  807. QVector<QColor> ThemeManager::generateAntdPalette(const QColor &baseColor) const
  808. {
  809. QVector<QColor> palette;
  810. QColor base = baseColor.toHsv();
  811. qreal h, s, v;
  812. base.getHsvF(&h, &s, &v);
  813. h = qRound(h * 360);
  814. auto getHue = [](qreal hsv_h, int i, bool light) -> qreal {
  815. qreal hue;
  816. if (qRound(hsv_h) >= 60 && qRound(hsv_h) <= 240) {
  817. hue = light ? qRound(hsv_h) - hueStep * i : qRound(hsv_h) + hueStep * i;
  818. } else {
  819. hue = light ? qRound(hsv_h) + hueStep * i : qRound(hsv_h) - hueStep * i;
  820. }
  821. return std::fmod(hue + 360, 360);
  822. };
  823. auto getSaturation = [](qreal hsv_s, int i, bool light) -> qreal {
  824. // 灰色不改变饱和度
  825. if (qFuzzyIsNull(hsv_s)) {
  826. return hsv_s;
  827. }
  828. qreal saturation;
  829. if (light) {
  830. saturation = hsv_s - saturationStep * i;
  831. } else if (i == darkColorCount) {
  832. saturation = hsv_s + saturationStep;
  833. } else {
  834. saturation = hsv_s + saturationStep2 * i;
  835. }
  836. // 边界值修正
  837. if (saturation > 1) {
  838. saturation = 1;
  839. }
  840. // 第一格的 s 限制在 0.06-0.1 之间
  841. if (light && i == lightColorCount && saturation > 0.1) {
  842. saturation = 0.1;
  843. }
  844. if (saturation < 0.06) {
  845. saturation = 0.06;
  846. }
  847. return saturation;
  848. };
  849. auto getValue = [](qreal hsv_v, int i, bool light) -> qreal {
  850. qreal value;
  851. if (light) {
  852. value = hsv_v + brightnessStep1 * i;
  853. } else {
  854. value = hsv_v - brightnessStep2 * i;
  855. }
  856. return qBound(0.0, value, 1.0);
  857. };
  858. // 生成浅色系
  859. for (int i = lightColorCount; i > 0; --i) {
  860. qreal newH = getHue(h, i, true);
  861. qreal newS = getSaturation(s, i, true);
  862. qreal newV = getValue(v, i, true);
  863. palette.append(QColor::fromHsvF(newH / 360.0, newS, newV));
  864. }
  865. // 添加主色
  866. palette.append(baseColor);
  867. // 生成深色系
  868. for (int i = 1; i <= darkColorCount; ++i) {
  869. qreal newH = getHue(h, i, false);
  870. qreal newS = getSaturation(s, i, false);
  871. qreal newV = getValue(v, i, false);
  872. palette.append(QColor::fromHsvF(newH / 360.0, newS, newV));
  873. }
  874. // 暗色主题处理
  875. if (m_themeMode == Dark) {
  876. return applyDarkMap(palette);
  877. }
  878. return palette;
  879. }
  880. QVector<QColor> ThemeManager::applyDarkMap(const QVector<QColor> &palette,
  881. const QColor &backgroundColor) const
  882. {
  883. QVector<QColor> darkPalette;
  884. QColor bgColor = backgroundColor;
  885. if (!bgColor.isValid()) {
  886. bgColor = QColor("#141414");
  887. }
  888. for (int i = 0; i < palette.size(); ++i) {
  889. darkPalette.append(mixColor(bgColor, palette[darkColorMap[i]], darkMixRatios[i]));
  890. }
  891. return darkPalette;
  892. }
  893. void ThemeManager::generateNeutralColorPalettes(const QColor &baseColor, const QColor &textBaseColor)
  894. {
  895. QColor colorBgBase = baseColor;
  896. if (!colorBgBase.isValid()) {
  897. if (m_themeMode == Light) {
  898. colorBgBase = QColor("#FFFFFFFF");
  899. } else {
  900. colorBgBase = QColor("#000");
  901. }
  902. }
  903. QColor colorTextBase = textBaseColor;
  904. if (!colorTextBase.isValid()) {
  905. if (m_themeMode == Light) {
  906. colorTextBase = QColor("#000");
  907. } else {
  908. colorTextBase = QColor("#FFFFFFFF");
  909. }
  910. }
  911. m_colorTokens[ColorToken::colorBgBase] = colorBgBase;
  912. m_colorTokens[ColorToken::colorTextBase] = colorTextBase;
  913. auto getAlphaColor = [](const QColor &baseColor, qreal alpha) {
  914. QColor result = baseColor;
  915. result.setAlphaF(alpha);
  916. return result;
  917. };
  918. auto getSolidColor = [this](const QColor &baseColor, int amount) {
  919. // if (m_themeMode == Dark) {
  920. // return baseColor.darker(amount);
  921. // } else {
  922. // return baseColor.lighter(amount);
  923. // }
  924. QColor hsl = baseColor.toHsl();
  925. qreal h = hsl.hslHueF(); // 0.0 <= hueF < 360.0
  926. qreal s = hsl.hslSaturationF();
  927. qreal l = hsl.lightnessF();
  928. if (m_themeMode == Dark) {
  929. l = l + amount / 100.0;
  930. if (l > 1) {
  931. l = 1;
  932. }
  933. } else {
  934. l = l - amount / 100.0;
  935. if (l < 0) {
  936. l = 0;
  937. }
  938. }
  939. return QColor::fromHslF(h, s, l, baseColor.alphaF());
  940. };
  941. if (m_themeMode == Dark) {
  942. m_colorTokens[ColorToken::colorText] = getAlphaColor(colorTextBase, 0.85);
  943. m_colorTokens[ColorToken::colorTextSecondary] = getAlphaColor(colorTextBase, 0.65);
  944. m_colorTokens[ColorToken::colorTextTertiary] = getAlphaColor(colorTextBase, 0.45);
  945. m_colorTokens[ColorToken::colorTextQuaternary] = getAlphaColor(colorTextBase, 0.25);
  946. m_colorTokens[ColorToken::colorFill] = getAlphaColor(colorTextBase, 0.18);
  947. m_colorTokens[ColorToken::colorFillSecondary] = getAlphaColor(colorTextBase, 0.12);
  948. m_colorTokens[ColorToken::colorFillTertiary] = getAlphaColor(colorTextBase, 0.08);
  949. m_colorTokens[ColorToken::colorFillQuaternary] = getAlphaColor(colorTextBase, 0.04);
  950. m_colorTokens[ColorToken::colorBgSolid] = getAlphaColor(colorTextBase, 0.95);
  951. m_colorTokens[ColorToken::colorBgSolidHover] = getAlphaColor(colorTextBase, 1);
  952. m_colorTokens[ColorToken::colorBgSolidActive] = getAlphaColor(colorTextBase, 0.9);
  953. m_colorTokens[ColorToken::colorBgElevated] = getSolidColor(colorBgBase, 12);
  954. m_colorTokens[ColorToken::colorBgContainer] = getSolidColor(colorBgBase, 8);
  955. m_colorTokens[ColorToken::colorBgLayout] = getSolidColor(colorBgBase, 0);
  956. m_colorTokens[ColorToken::colorBgSpotlight] = getSolidColor(colorBgBase, 26);
  957. m_colorTokens[ColorToken::colorBgBlur] = getAlphaColor(colorTextBase, 0.04);
  958. m_colorTokens[ColorToken::colorBorder] = getSolidColor(colorBgBase, 26);
  959. m_colorTokens[ColorToken::colorBorderSecondary] = getSolidColor(colorBgBase, 19);
  960. } else {
  961. m_colorTokens[ColorToken::colorText] = getAlphaColor(colorTextBase, 0.88);
  962. m_colorTokens[ColorToken::colorTextSecondary] = getAlphaColor(colorTextBase, 0.65);
  963. m_colorTokens[ColorToken::colorTextTertiary] = getAlphaColor(colorTextBase, 0.45);
  964. m_colorTokens[ColorToken::colorTextQuaternary] = getAlphaColor(colorTextBase, 0.25);
  965. m_colorTokens[ColorToken::colorFill] = getAlphaColor(colorTextBase, 0.15);
  966. m_colorTokens[ColorToken::colorFillSecondary] = getAlphaColor(colorTextBase, 0.06);
  967. m_colorTokens[ColorToken::colorFillTertiary] = getAlphaColor(colorTextBase, 0.04);
  968. m_colorTokens[ColorToken::colorFillQuaternary] = getAlphaColor(colorTextBase, 0.02);
  969. m_colorTokens[ColorToken::colorBgSolid] = getAlphaColor(colorTextBase, 1);
  970. m_colorTokens[ColorToken::colorBgSolidHover] = getAlphaColor(colorTextBase, 0.75);
  971. m_colorTokens[ColorToken::colorBgSolidActive] = getAlphaColor(colorTextBase, 0.95);
  972. m_colorTokens[ColorToken::colorBgLayout] = getSolidColor(colorBgBase, 4);
  973. m_colorTokens[ColorToken::colorBgContainer] = getSolidColor(colorBgBase, 0);
  974. m_colorTokens[ColorToken::colorBgElevated] = getSolidColor(colorBgBase, 0);
  975. m_colorTokens[ColorToken::colorBgSpotlight] = getAlphaColor(colorTextBase, 0.85);
  976. m_colorTokens[ColorToken::colorBgBlur] = "transparent";
  977. m_colorTokens[ColorToken::colorBorder] = getSolidColor(colorBgBase, 15);
  978. m_colorTokens[ColorToken::colorBorderSecondary] = getSolidColor(colorBgBase, 6);
  979. }
  980. }
  981. QColor ThemeManager::mixColor(const QColor &base, const QColor &target, int ratio) const
  982. {
  983. const qreal p = ratio / 100.0;
  984. qreal r = (base.red() - target.red()) * p + target.red();
  985. qreal g = (base.green() - target.green()) * p + target.green();
  986. qreal b = (base.blue() - target.blue()) * p + target.blue();
  987. return QColor(r, g, b);
  988. }
  989. QMap<ThemeManager::ControlHeightToken, int> ThemeManager::genControlHeight(const SeedTokens &token)
  990. {
  991. const int controlHeight = token.controlHeight;
  992. return {
  993. {controlHeightSM, controlHeight * 0.75},
  994. {controlHeightXS, controlHeight * 0.5},
  995. {controlHeightLG, controlHeight * 1.25},
  996. };
  997. }
  998. QMap<ThemeManager::SizeToken, int> ThemeManager::genSizeMapToken(const SeedTokens &token)
  999. {
  1000. const int sizeUnit = token.sizeUnit;
  1001. const int sizeStep = token.sizeStep;
  1002. const int compactSizeStep = sizeStep - 2;
  1003. return {
  1004. {SizeXXL, sizeUnit * (compactSizeStep + 10)},
  1005. {SizeXL, sizeUnit * (compactSizeStep + 6)},
  1006. {SizeLG, sizeUnit * (compactSizeStep + 2)},
  1007. {SizeMD, sizeUnit * (compactSizeStep + 2)},
  1008. {SizeMS, sizeUnit * (compactSizeStep + 1)},
  1009. {size, sizeUnit * compactSizeStep},
  1010. {SizeSM, sizeUnit * compactSizeStep},
  1011. {SizeXS, sizeUnit * (compactSizeStep - 1)},
  1012. {SizeXXS, sizeUnit * (compactSizeStep - 1)},
  1013. };
  1014. }