thememanager.cpp 34 KB

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