window_themes_embedded.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "window/themes/window_themes_embedded.h"
  8. #include "window/themes/window_theme.h"
  9. #include "lang/lang_keys.h"
  10. #include "storage/serialize_common.h"
  11. #include "core/application.h"
  12. #include "core/core_settings.h"
  13. #include "ui/style/style_palette_colorizer.h"
  14. namespace Window {
  15. namespace Theme {
  16. namespace {
  17. constexpr auto kMaxAccentColors = 3;
  18. constexpr auto kDayBaseFile = ":/gui/day-custom-base.tdesktop-theme"_cs;
  19. constexpr auto kNightBaseFile = ":/gui/night-custom-base.tdesktop-theme"_cs;
  20. const auto kColorizeIgnoredKeys = base::flat_set<QLatin1String>{ {
  21. qstr("boxTextFgGood"),
  22. qstr("boxTextFgError"),
  23. qstr("callIconFg"),
  24. qstr("historyPeer1NameFg"),
  25. qstr("historyPeer1NameFgSelected"),
  26. qstr("historyPeer1UserpicBg"),
  27. qstr("historyPeer2NameFg"),
  28. qstr("historyPeer2NameFgSelected"),
  29. qstr("historyPeer2UserpicBg"),
  30. qstr("historyPeer3NameFg"),
  31. qstr("historyPeer3NameFgSelected"),
  32. qstr("historyPeer3UserpicBg"),
  33. qstr("historyPeer4NameFg"),
  34. qstr("historyPeer4NameFgSelected"),
  35. qstr("historyPeer4UserpicBg"),
  36. qstr("historyPeer5NameFg"),
  37. qstr("historyPeer5NameFgSelected"),
  38. qstr("historyPeer5UserpicBg"),
  39. qstr("historyPeer6NameFg"),
  40. qstr("historyPeer6NameFgSelected"),
  41. qstr("historyPeer6UserpicBg"),
  42. qstr("historyPeer7NameFg"),
  43. qstr("historyPeer7NameFgSelected"),
  44. qstr("historyPeer7UserpicBg"),
  45. qstr("historyPeer8NameFg"),
  46. qstr("historyPeer8NameFgSelected"),
  47. qstr("historyPeer8UserpicBg"),
  48. qstr("historyPeer1UserpicBg2"),
  49. qstr("historyPeer2UserpicBg2"),
  50. qstr("historyPeer3UserpicBg2"),
  51. qstr("historyPeer4UserpicBg2"),
  52. qstr("historyPeer5UserpicBg2"),
  53. qstr("historyPeer6UserpicBg2"),
  54. qstr("historyPeer7UserpicBg2"),
  55. qstr("historyPeer8UserpicBg2"),
  56. qstr("msgFile1Bg"),
  57. qstr("msgFile1BgDark"),
  58. qstr("msgFile1BgOver"),
  59. qstr("msgFile1BgSelected"),
  60. qstr("msgFile2Bg"),
  61. qstr("msgFile2BgDark"),
  62. qstr("msgFile2BgOver"),
  63. qstr("msgFile2BgSelected"),
  64. qstr("msgFile3Bg"),
  65. qstr("msgFile3BgDark"),
  66. qstr("msgFile3BgOver"),
  67. qstr("msgFile3BgSelected"),
  68. qstr("msgFile4Bg"),
  69. qstr("msgFile4BgDark"),
  70. qstr("msgFile4BgOver"),
  71. qstr("msgFile4BgSelected"),
  72. qstr("mediaviewFileRedCornerFg"),
  73. qstr("mediaviewFileYellowCornerFg"),
  74. qstr("mediaviewFileGreenCornerFg"),
  75. qstr("mediaviewFileBlueCornerFg"),
  76. qstr("settingsIconBg1"),
  77. qstr("settingsIconBg2"),
  78. qstr("settingsIconBg3"),
  79. qstr("settingsIconBg4"),
  80. qstr("settingsIconBg5"),
  81. qstr("settingsIconBg6"),
  82. qstr("settingsIconBg8"),
  83. qstr("settingsIconBgArchive"),
  84. qstr("premiumButtonBg1"),
  85. qstr("premiumButtonBg2"),
  86. qstr("premiumButtonBg3"),
  87. qstr("premiumIconBg1"),
  88. qstr("premiumIconBg2"),
  89. } };
  90. style::colorizer::Color cColor(std::string_view hex) {
  91. const auto q = style::ColorFromHex(hex);
  92. auto hue = int();
  93. auto saturation = int();
  94. auto value = int();
  95. q.getHsv(&hue, &saturation, &value);
  96. return style::colorizer::Color{ hue, saturation, value };
  97. }
  98. } // namespace
  99. style::colorizer ColorizerFrom(
  100. const EmbeddedScheme &scheme,
  101. const QColor &color) {
  102. using Color = style::colorizer::Color;
  103. using Pair = std::pair<Color, Color>;
  104. auto result = style::colorizer();
  105. result.ignoreKeys = kColorizeIgnoredKeys;
  106. result.hueThreshold = 15;
  107. scheme.accentColor.getHsv(
  108. &result.was.hue,
  109. &result.was.saturation,
  110. &result.was.value);
  111. color.getHsv(
  112. &result.now.hue,
  113. &result.now.saturation,
  114. &result.now.value);
  115. switch (scheme.type) {
  116. case EmbeddedType::Default:
  117. result.lightnessMax = 160;
  118. break;
  119. case EmbeddedType::DayBlue:
  120. result.lightnessMax = 160;
  121. break;
  122. case EmbeddedType::Night:
  123. result.keepContrast = base::flat_map<QLatin1String, Pair>{ {
  124. //{ qstr("windowFgActive"), Pair{ cColor("5288c1"), cColor("17212b") } }, // windowBgActive
  125. { qstr("activeButtonFg"), Pair{ cColor("2f6ea5"), cColor("17212b") } }, // activeButtonBg
  126. { qstr("profileVerifiedCheckFg"), Pair{ cColor("5288c1"), cColor("17212b") } }, // profileVerifiedCheckBg
  127. { qstr("overviewCheckFgActive"), Pair{ cColor("5288c1"), cColor("17212b") } }, // overviewCheckBgActive
  128. { qstr("historyFileInIconFg"), Pair{ cColor("3f96d0"), cColor("182533") } }, // msgFileInBg, msgInBg
  129. { qstr("historyFileInIconFgSelected"), Pair{ cColor("6ab4f4"), cColor("2e70a5") } }, // msgFileInBgSelected, msgInBgSelected
  130. { qstr("historyFileInRadialFg"), Pair{ cColor("3f96d0"), cColor("182533") } }, // msgFileInBg, msgInBg
  131. { qstr("historyFileInRadialFgSelected"), Pair{ cColor("6ab4f4"), cColor("2e70a5") } }, // msgFileInBgSelected, msgInBgSelected
  132. { qstr("historyFileOutIconFg"), Pair{ cColor("4c9ce2"), cColor("2b5278") } }, // msgFileOutBg, msgOutBg
  133. { qstr("historyFileOutIconFgSelected"), Pair{ cColor("58abf3"), cColor("2e70a5") } }, // msgFileOutBgSelected, msgOutBgSelected
  134. { qstr("historyFileOutRadialFg"), Pair{ cColor("4c9ce2"), cColor("2b5278") } }, // msgFileOutBg, msgOutBg
  135. { qstr("historyFileOutRadialFgSelected"), Pair{ cColor("58abf3"), cColor("2e70a5") } }, // msgFileOutBgSelected, msgOutBgSelected
  136. } };
  137. result.lightnessMin = 64;
  138. break;
  139. case EmbeddedType::NightGreen:
  140. result.keepContrast = base::flat_map<QLatin1String, Pair>{ {
  141. //{ qstr("windowFgActive"), Pair{ cColor("3fc1b0"), cColor("282e33") } }, // windowBgActive, windowBg
  142. { qstr("activeButtonFg"), Pair{ cColor("2da192"), cColor("282e33") } }, // activeButtonBg, windowBg
  143. { qstr("profileVerifiedCheckFg"), Pair{ cColor("3fc1b0"), cColor("282e33") } }, // profileVerifiedCheckBg, windowBg
  144. { qstr("overviewCheckFgActive"), Pair{ cColor("3fc1b0"), cColor("282e33") } }, // overviewCheckBgActive
  145. // callIconFg is used not only over callAnswerBg,
  146. // so this contrast-forcing breaks other buttons.
  147. //{ qstr("callIconFg"), Pair{ cColor("5ad1c1"), cColor("1b1f23") } }, // callAnswerBg, callBgOpaque
  148. } };
  149. result.lightnessMin = 64;
  150. break;
  151. }
  152. const auto nowLightness = color.lightness();
  153. const auto limitedLightness = std::clamp(
  154. nowLightness,
  155. result.lightnessMin,
  156. result.lightnessMax);
  157. if (limitedLightness != nowLightness) {
  158. QColor::fromHsl(
  159. color.hslHue(),
  160. color.hslSaturation(),
  161. limitedLightness).getHsv(
  162. &result.now.hue,
  163. &result.now.saturation,
  164. &result.now.value);
  165. }
  166. return result;
  167. }
  168. style::colorizer ColorizerForTheme(const QString &absolutePath) {
  169. if (!IsEmbeddedTheme(absolutePath)) {
  170. return {};
  171. }
  172. const auto schemes = EmbeddedThemes();
  173. const auto i = ranges::find(
  174. schemes,
  175. absolutePath,
  176. &EmbeddedScheme::path);
  177. if (i == end(schemes)) {
  178. return {};
  179. }
  180. const auto &colors = Core::App().settings().themesAccentColors();
  181. if (const auto accent = colors.get(i->type)) {
  182. return ColorizerFrom(*i, *accent);
  183. }
  184. return {};
  185. }
  186. void Colorize(EmbeddedScheme &scheme, const style::colorizer &colorizer) {
  187. const auto colors = {
  188. &EmbeddedScheme::background,
  189. &EmbeddedScheme::sent,
  190. &EmbeddedScheme::received,
  191. &EmbeddedScheme::radiobuttonActive,
  192. &EmbeddedScheme::radiobuttonInactive
  193. };
  194. for (const auto color : colors) {
  195. if (const auto changed = style::colorize(scheme.*color, colorizer)) {
  196. scheme.*color = changed->toRgb();
  197. }
  198. }
  199. }
  200. std::vector<EmbeddedScheme> EmbeddedThemes() {
  201. const auto qColor = [](auto hex) {
  202. return style::ColorFromHex(hex);
  203. };
  204. const auto name = [](auto key) {
  205. return rpl::deferred([=] { return key(); });
  206. };
  207. return {
  208. EmbeddedScheme{
  209. EmbeddedType::Default,
  210. qColor("9bd494"),
  211. qColor("eaffdc"),
  212. qColor("ffffff"),
  213. qColor("eaffdc"),
  214. qColor("ffffff"),
  215. name(tr::lng_settings_theme_classic),
  216. QString(),
  217. qColor("40a7e3")
  218. },
  219. EmbeddedScheme{
  220. EmbeddedType::DayBlue,
  221. qColor("7ec4ea"),
  222. qColor("d7f0ff"),
  223. qColor("ffffff"),
  224. qColor("d7f0ff"),
  225. qColor("ffffff"),
  226. name(tr::lng_settings_theme_day),
  227. ":/gui/day-blue.tdesktop-theme",
  228. qColor("40a7e3")
  229. },
  230. EmbeddedScheme{
  231. EmbeddedType::Night,
  232. qColor("485761"),
  233. qColor("5ca7d4"),
  234. qColor("6b808d"),
  235. qColor("6b808d"),
  236. qColor("5ca7d4"),
  237. name(tr::lng_settings_theme_tinted),
  238. ":/gui/night.tdesktop-theme",
  239. qColor("5288c1")
  240. },
  241. EmbeddedScheme{
  242. EmbeddedType::NightGreen,
  243. qColor("485761"),
  244. qColor("6b808d"),
  245. qColor("6b808d"),
  246. qColor("6b808d"),
  247. qColor("75bfb5"),
  248. name(tr::lng_settings_theme_night),
  249. ":/gui/night-green.tdesktop-theme",
  250. qColor("3fc1b0")
  251. },
  252. };
  253. }
  254. std::vector<QColor> DefaultAccentColors(EmbeddedType type) {
  255. const auto qColor = [](auto hex) {
  256. return style::ColorFromHex(hex);
  257. };
  258. switch (type) {
  259. case EmbeddedType::DayBlue:
  260. return {
  261. qColor("45bce7"),
  262. qColor("52b440"),
  263. qColor("d46c99"),
  264. qColor("df8a49"),
  265. qColor("9978c8"),
  266. qColor("c55245"),
  267. qColor("687b98"),
  268. qColor("dea922"),
  269. };
  270. case EmbeddedType::Default:
  271. return {
  272. qColor("45bce7"),
  273. qColor("52b440"),
  274. qColor("d46c99"),
  275. qColor("df8a49"),
  276. qColor("9978c8"),
  277. qColor("c55245"),
  278. qColor("687b98"),
  279. qColor("dea922"),
  280. };
  281. case EmbeddedType::Night:
  282. return {
  283. qColor("58bfe8"),
  284. qColor("466f42"),
  285. qColor("aa6084"),
  286. qColor("a46d3c"),
  287. qColor("917bbd"),
  288. qColor("ab5149"),
  289. qColor("697b97"),
  290. qColor("9b834b"),
  291. };
  292. case EmbeddedType::NightGreen:
  293. return {
  294. qColor("60a8e7"),
  295. qColor("4e9c57"),
  296. qColor("ca7896"),
  297. qColor("cc925c"),
  298. qColor("a58ed2"),
  299. qColor("d27570"),
  300. qColor("7b8799"),
  301. qColor("cbac67"),
  302. };
  303. }
  304. Unexpected("Type in Window::Theme::AccentColors.");
  305. }
  306. Fn<void(style::palette&)> PreparePaletteCallback(
  307. bool dark,
  308. std::optional<QColor> accent) {
  309. return [=](style::palette &palette) {
  310. using namespace Theme;
  311. const auto &embedded = EmbeddedThemes();
  312. const auto i = ranges::find(
  313. embedded,
  314. dark ? EmbeddedType::Night : EmbeddedType::Default,
  315. &EmbeddedScheme::type);
  316. Assert(i != end(embedded));
  317. const auto colorizer = accent
  318. ? ColorizerFrom(*i, *accent)
  319. : style::colorizer();
  320. auto instance = Instance();
  321. const auto loaded = LoadFromFile(
  322. (dark ? kNightBaseFile : kDayBaseFile).utf16(),
  323. &instance,
  324. nullptr,
  325. nullptr,
  326. colorizer);
  327. Assert(loaded);
  328. palette.finalize();
  329. palette = instance.palette;
  330. };
  331. }
  332. Fn<void(style::palette&)> PrepareCurrentPaletteCallback() {
  333. return [=, data = style::main_palette::save()](style::palette &palette) {
  334. palette.load(data);
  335. };
  336. }
  337. QByteArray AccentColors::serialize() const {
  338. auto result = QByteArray();
  339. if (_data.empty()) {
  340. return result;
  341. }
  342. const auto count = _data.size();
  343. auto size = sizeof(qint32) * (count + 1)
  344. + Serialize::colorSize() * count;
  345. result.reserve(size);
  346. auto stream = QDataStream(&result, QIODevice::WriteOnly);
  347. stream.setVersion(QDataStream::Qt_5_1);
  348. stream << qint32(_data.size());
  349. for (const auto &[type, color] : _data) {
  350. stream << static_cast<qint32>(type);
  351. Serialize::writeColor(stream, color);
  352. }
  353. stream.device()->close();
  354. return result;
  355. }
  356. bool AccentColors::setFromSerialized(const QByteArray &serialized) {
  357. if (serialized.isEmpty()) {
  358. _data.clear();
  359. return true;
  360. }
  361. auto copy = QByteArray(serialized);
  362. auto stream = QDataStream(&copy, QIODevice::ReadOnly);
  363. stream.setVersion(QDataStream::Qt_5_1);
  364. auto count = qint32();
  365. stream >> count;
  366. if (stream.status() != QDataStream::Ok) {
  367. return false;
  368. } else if (count <= 0 || count > kMaxAccentColors) {
  369. return false;
  370. }
  371. auto data = base::flat_map<EmbeddedType, QColor>();
  372. for (auto i = 0; i != count; ++i) {
  373. auto type = qint32();
  374. stream >> type;
  375. const auto color = Serialize::readColor(stream);
  376. const auto uncheckedType = static_cast<EmbeddedType>(type);
  377. switch (uncheckedType) {
  378. case EmbeddedType::Default:
  379. case EmbeddedType::DayBlue:
  380. case EmbeddedType::Night:
  381. case EmbeddedType::NightGreen:
  382. data.emplace(uncheckedType, color);
  383. break;
  384. default:
  385. return false;
  386. }
  387. }
  388. if (stream.status() != QDataStream::Ok) {
  389. return false;
  390. }
  391. _data = std::move(data);
  392. return true;
  393. }
  394. void AccentColors::set(EmbeddedType type, const QColor &value) {
  395. _data.emplace_or_assign(type, value);
  396. }
  397. void AccentColors::clear(EmbeddedType type) {
  398. _data.remove(type);
  399. }
  400. std::optional<QColor> AccentColors::get(EmbeddedType type) const {
  401. const auto i = _data.find(type);
  402. return (i != end(_data)) ? std::make_optional(i->second) : std::nullopt;
  403. }
  404. } // namespace Theme
  405. } // namespace Window