diff --git a/Telegram/Resources/lang.strings b/Telegram/Resources/lang.strings index 3175d7f71..8b1e110fd 100644 --- a/Telegram/Resources/lang.strings +++ b/Telegram/Resources/lang.strings @@ -407,6 +407,16 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org "lng_media_video" = "Video file"; "lng_media_audio" = "Voice message"; +"lng_emoji_category0" = "Frequently used"; +"lng_emoji_category1" = "People"; +"lng_emoji_category2" = "Nature"; +"lng_emoji_category3" = "Food & Drink"; +"lng_emoji_category4" = "Celebration"; +"lng_emoji_category5" = "Activity"; +"lng_emoji_category6" = "Travel & Places"; +"lng_emoji_category7" = "Objects & Symbols"; +"lng_emoji_category8" = "Stickers"; + "lng_in_dlg_photo" = "Photo"; "lng_in_dlg_video" = "Video"; "lng_in_dlg_contact" = "Contact"; diff --git a/Telegram/Resources/style.txt b/Telegram/Resources/style.txt index 18ce6aba0..785b9c624 100644 --- a/Telegram/Resources/style.txt +++ b/Telegram/Resources/style.txt @@ -21,7 +21,6 @@ semibold: 'Open Sans Semibold'; fsize: 13px; spriteFile: ':/gui/art/sprite.png' / 2:':/gui/art/sprite_125x.png' / 3:':/gui/art/sprite_150x.png' / 4:':/gui/art/sprite_200x.png'; -emojisFile: ':/gui/art/emoji.png' / 2:':/gui/art/emoji_125x.png' / 3:':/gui/art/emoji_150x.png' / 4:':/gui/art/emoji_200x.png'; emojiImgSize: 18px; // exceptional value for retina emojiSize: 18px; emojiPadding: 0px; @@ -123,9 +122,9 @@ sysUnlock: sysButton(sysUpd) { img: sprite(207px, 22px, 19px, 19px); } titleBackButton: iconedButton(btnDefIconed) { - icon: sprite(133px, 197px, 13px, 20px); + icon: sprite(113px, 108px, 13px, 20px); iconPos: point(5px, 9px); - downIcon: sprite(133px, 197px, 13px, 20px); + downIcon: sprite(113px, 108px, 13px, 20px); downIconPos: point(5px, 10px); bgColor: #c4d8e9; @@ -991,7 +990,7 @@ replyHeight: 49px; replyTop: 8px; replyBottom: 6px; replyIconPos: point(13px, 13px); -replyIcon: sprite(174px, 195px, 24px, 24px); +replyIcon: sprite(343px, 197px, 24px, 24px); replyCancel: iconedButton(btnDefIconed) { icon: sprite(165px, 24px, 14px, 14px); iconPos: point(17px, 17px); @@ -1002,7 +1001,7 @@ replyCancel: iconedButton(btnDefIconed) { width: 49px; height: 49px; } -forwardIcon: sprite(368px, 173px, 24px, 24px); +forwardIcon: sprite(368px, 197px, 24px, 24px); historyScroll: flatScroll(scrollDef) { barColor: #89a0b47a; @@ -1453,33 +1452,39 @@ emojiScroll: flatScroll(scrollDef) { topsh: 0px; bottomsh: 0px; } -emojiRecentActive: sprite(290px, 287px, 20px, 20px); -emojiRecentOver: sprite(311px, 287px, 20px, 20px); emojiRecent: sprite(6px, 197px, 20px, 20px); -emojiPeopleActive: sprite(290px, 221px, 20px, 20px); -emojiPeopleOver: sprite(311px, 221px, 20px, 20px); +emojiRecentOver: sprite(290px, 221px, 20px, 20px); +emojiRecentActive: sprite(290px, 242px, 20px, 20px); emojiPeople: sprite(27px, 197px, 20px, 20px); -emojiNatureActive: sprite(245px, 266px, 20px, 20px); -emojiNatureOver: sprite(266px, 266px, 20px, 20px); +emojiPeopleOver: sprite(311px, 221px, 20px, 20px); +emojiPeopleActive: sprite(311px, 242px, 20px, 20px); emojiNature: sprite(48px, 197px, 20px, 20px); -emojiObjectsActive: sprite(290px, 242px, 20px, 20px); -emojiObjectsOver: sprite(311px, 242px, 20px, 20px); -emojiObjects: sprite(69px, 197px, 20px, 20px); -emojiPlacesActive: sprite(245px, 287px, 20px, 20px); -emojiPlacesOver: sprite(266px, 287px, 20px, 20px); -emojiPlaces: sprite(90px, 197px, 20px, 20px); -emojiSymbolsActive: sprite(290px, 266px, 20px, 20px); -emojiSymbolsOver: sprite(311px, 266px, 20px, 20px); -emojiSymbols: sprite(111px, 197px, 20px, 20px); -emojiStickersActive: sprite(311px, 308px, 20px, 20px); -emojiStickersOver: sprite(354px, 200px, 20px, 20px); -emojiStickers: sprite(375px, 200px, 20px, 20px); +emojiNatureOver: sprite(245px, 266px, 20px, 20px); +emojiNatureActive: sprite(245px, 287px, 20px, 20px); +emojiFood: sprite(69px, 197px, 20px, 20px); +emojiFoodOver: sprite(266px, 266px, 20px, 20px); +emojiFoodActive: sprite(266px, 287px, 20px, 20px); +emojiCelebration: sprite(90px, 197px, 20px, 20px); +emojiCelebrationOver: sprite(290px, 266px, 20px, 20px); +emojiCelebrationActive: sprite(290px, 287px, 20px, 20px); +emojiActivity: sprite(111px, 197px, 20px, 20px); +emojiActivityOver: sprite(311px, 266px, 20px, 20px); +emojiActivityActive: sprite(311px, 287px, 20px, 20px); +emojiTravel: sprite(132px, 197px, 20px, 20px); +emojiTravelOver: sprite(321px, 344px, 20px, 20px); +emojiTravelActive: sprite(321px, 365px, 20px, 20px); +emojiObjects: sprite(153px, 197px, 20px, 20px); +emojiObjectsOver: sprite(342px, 344px, 20px, 20px); +emojiObjectsActive: sprite(342px, 365px, 20px, 20px); +emojiStickers: sprite(174px, 197px, 20px, 20px); +emojiStickersOver: sprite(363px, 344px, 20px, 20px); +emojiStickersActive: sprite(363px, 365px, 20px, 20px); rbEmoji: flatCheckbox { textColor: transparent; bgColor: transparent; disColor: transparent; - width: 29px; + width: 28px; height: 36px; textTop: 0px; @@ -1516,6 +1521,38 @@ rbEmojiNature: flatCheckbox(rbEmoji) { disImageRect: emojiNature; chkDisImageRect: emojiNatureActive; } +rbEmojiFood: flatCheckbox(rbEmoji) { + imageRect: emojiFood; + chkImageRect: emojiFoodActive; + overImageRect: emojiFoodOver; + chkOverImageRect: emojiFoodActive; + disImageRect: emojiFood; + chkDisImageRect: emojiFoodActive; +} +rbEmojiCelebration: flatCheckbox(rbEmoji) { + imageRect: emojiCelebration; + chkImageRect: emojiCelebrationActive; + overImageRect: emojiCelebrationOver; + chkOverImageRect: emojiCelebrationActive; + disImageRect: emojiCelebration; + chkDisImageRect: emojiCelebrationActive; +} +rbEmojiActivity: flatCheckbox(rbEmoji) { + imageRect: emojiActivity; + chkImageRect: emojiActivityActive; + overImageRect: emojiActivityOver; + chkOverImageRect: emojiActivityActive; + disImageRect: emojiActivity; + chkDisImageRect: emojiActivityActive; +} +rbEmojiTravel: flatCheckbox(rbEmoji) { + imageRect: emojiTravel; + chkImageRect: emojiTravelActive; + overImageRect: emojiTravelOver; + chkOverImageRect: emojiTravelActive; + disImageRect: emojiTravel; + chkDisImageRect: emojiTravelActive; +} rbEmojiObjects: flatCheckbox(rbEmoji) { imageRect: emojiObjects; chkImageRect: emojiObjectsActive; @@ -1524,22 +1561,6 @@ rbEmojiObjects: flatCheckbox(rbEmoji) { disImageRect: emojiObjects; chkDisImageRect: emojiObjectsActive; } -rbEmojiPlaces: flatCheckbox(rbEmoji) { - imageRect: emojiPlaces; - chkImageRect: emojiPlacesActive; - overImageRect: emojiPlacesOver; - chkOverImageRect: emojiPlacesActive; - disImageRect: emojiPlaces; - chkDisImageRect: emojiPlacesActive; -} -rbEmojiSymbols: flatCheckbox(rbEmoji) { - imageRect: emojiSymbols; - chkImageRect: emojiSymbolsActive; - overImageRect: emojiSymbolsOver; - chkOverImageRect: emojiSymbolsActive; - disImageRect: emojiSymbols; - chkDisImageRect: emojiSymbolsActive; -} rbEmojiStickers: flatCheckbox(rbEmojiRecent) { imageRect: emojiStickers; chkImageRect: emojiStickersActive; @@ -1549,15 +1570,25 @@ rbEmojiStickers: flatCheckbox(rbEmojiRecent) { chkDisImageRect: emojiStickersActive; } emojiPanPadding: margins(5px, 0px, 0px, 5px); -emojiPanSize: size(28px, 28px); -emojiPanSub: 0px; +emojiPanSize: size(35px, 35px); emojiPanDuration: 200; emojiPanHover: #f0f0f0; emojiPanRound: 2px; +emojiPanHeader: 25px; +emojiPanHeaderFont: font(fsize semibold); +emojiPanHeaderColor: #999; +emojiPanHeaderLeft: 5px; +emojiPanHeaderTop: 5px; +emojiPanHeaderBg: #fffd; + +emojiColorsPadding: 5px; +emojiColorsSep: 1px; +emojiColorsSepColor: #d5d5d5; + stickerPanRound: 3px; stickerPanPadding: 2px; -stickerPanDelete: sprite(158px, 197px, 12px, 12px); +stickerPanDelete: sprite(123px, 132px, 12px, 12px); stickerPanDeleteOpacity: 0.5; mvBgColor: #222; @@ -1647,7 +1678,7 @@ mvDocLink: linkButton(btnDefLink) { mvDeltaFromLastAction: 5px; mvSwipeDistance: 80px; -medviewSaveMsgCheck: sprite(341px, 174px, 22px, 18px); +medviewSaveMsgCheck: sprite(311px, 309px, 22px, 18px); medviewSaveMsgFont: font(16px); medviewSaveMsgPadding: margins(55px, 19px, 29px, 20px); medviewSaveMsgCheckPos: point(23px, 21px); @@ -1657,7 +1688,7 @@ medviewSaveMsgShown: 2000; medviewSaveMsgHiding: 2500; medviewSaveMsg: #000000b2; -mvTransparentBrush: sprite(148px, 197px, 8px, 8px); +mvTransparentBrush: sprite(113px, 128px, 8px, 8px); overviewPhotoSkip: 10px; overviewPhotoMinSize: 100px; diff --git a/Telegram/SourceFiles/_other/genemoji.cpp b/Telegram/SourceFiles/_other/genemoji.cpp index 958792bc9..122f77a18 100644 --- a/Telegram/SourceFiles/_other/genemoji.cpp +++ b/Telegram/SourceFiles/_other/genemoji.cpp @@ -35,6 +35,8 @@ Q_IMPORT_PLUGIN(QTgaPlugin) Q_IMPORT_PLUGIN(QTiffPlugin) Q_IMPORT_PLUGIN(QWbmpPlugin) Q_IMPORT_PLUGIN(QWebpPlugin) +#else +#error Only Mac OS X is supported #endif typedef quint32 uint32; @@ -94,8 +96,10 @@ const uint32 replacesCount = sizeof(replaces) / sizeof(EmojiReplace); typedef QMap ReplaceMap; ReplaceMap replaceMap; -static const int variantsCount = 4, inRow = 40, imSizes[] = { 18, 22, 27, 36 }; -static const char *variantPostfix[] = { "", "_125x", "_150x", "_200x" }; +static const int variantsCount = 5, inRow = 40, imSizes[] = { 18, 22, 27, 36, 45 }; +static const int emojiFontSizes[] = { 14, 20, 27, 36, 45 }; +static const int emojiDeltas[] = { 15, 20, 25, 34, 42 }; +static const char *variantPostfix[] = { "", "_125x", "_150x", "_200x", "_250x" }; static const char *variantNames[] = { "dbisOne", "dbisOneAndQuarter", "dbisOneAndHalf", "dbisTwo" }; uint64 emojiColors[] = { @@ -1235,7 +1239,7 @@ void writeEmojiCategory(QTextStream &tcpp, uint64 *emojiCategory, uint32 size, c for (uint32 i = 0; i < size; ++i) { int index = 0; for (EmojisData::const_iterator j = emojisData.cbegin(), e = emojisData.cend(); j != e; ++j) { - if (j->code == firstCode(emojiCategory[i])) { + if (emojiCategory[i] == (j->code2 ? ((uint64(j->code) << 32) | j->code2) : j->code)) { break; } ++index; @@ -1377,8 +1381,6 @@ bool genEmoji(QString, const QString &emoji_out, const QString &emoji_png) { QStringList str = QFontDatabase::applicationFontFamilies(QFontDatabase::addApplicationFont(QStringLiteral("/System/Library/Fonts/Apple Color Emoji.ttf"))); - int emojiFontSizes[4] = { 14, 20, 27, 36 }; - int emojiDeltas[4] = { 15, 20, 25, 34 }; for (int variantIndex = 0; variantIndex < variantsCount; variantIndex++) { int imSize = imSizes[variantIndex]; @@ -1456,12 +1458,12 @@ bool genEmoji(QString, const QString &emoji_out, const QString &emoji_png) { p.drawImage(QRect(it->x * imSize, it->y * imSize, imSize, imSize), emojiImg, drawFrom); } } - QString postfix = variantPostfix[variantIndex], emojif = emoji_png + postfix + ".png"; + QString postfix = variantPostfix[variantIndex], emojif = emoji_png + postfix + ".webp"; QByteArray emojib; { QBuffer ebuf(&emojib); - if (!emojisImg.save(&ebuf, "PNG")) { - cout << "Could not save 'emoji" << postfix.toUtf8().constData() << ".png'!\n"; + if (!emojisImg.save(&ebuf, "WEBP", (variantIndex < 3) ? 100 : 99)) { + cout << "Could not save 'emoji" << postfix.toUtf8().constData() << ".webp'!\n"; return false; } } @@ -1530,7 +1532,13 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org\n\ tcpp << imSize; if (variantIndex + 1 < variantsCount) tcpp << ", "; } - tcpp << " }, ESize = 0;\n\n"; + tcpp << " }, EIndex = -1, ESize = 0;\n"; + tcpp << "const char *EmojiNames[] = { "; + for (int variantIndex = 0; variantIndex < variantsCount; ++variantIndex) { + tcpp << "\":/gui/art/emoji" << variantPostfix[variantIndex] << ".webp\""; + if (variantIndex + 1 < variantsCount) tcpp << ", "; + } + tcpp << " }, *EName = 0;\n"; int ind = 0; for (EmojisData::const_iterator i = emojisData.cbegin(), e = emojisData.cend(); i != e; ++i) { @@ -1549,14 +1557,16 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org\n\ ++ind; } - tcpp << "void initEmoji() {\n"; + tcpp << "void emojiInit() {\n"; tcpp << "\tDBIScale emojiForScale = cRetina() ? dbisTwo : cScale();\n\n"; tcpp << "\tswitch (emojiForScale) {\n"; - for (int variantIndex = 0; variantIndex < variantsCount; ++variantIndex) { - tcpp << "\t\tcase " << variantNames[variantIndex] << ": ESize = EmojiSizes[" << variantIndex << "]; break;\n"; + for (int variantIndex = 0; variantIndex < variantsCount - 1; ++variantIndex) { + tcpp << "\t\tcase " << variantNames[variantIndex] << ": EIndex = " << variantIndex << "; break;\n"; } - tcpp << "\t};\n\n"; + tcpp << "\t};\n\tESize = EmojiSizes[EIndex];\n\tEName = EmojiNames[EIndex];\n\n"; tcpp << "\tEmojiData *toFill = emojis = (EmojiData*)emojisData;\n\n"; + uint32 index = 0; + int sequenceOffset = 0; for (EmojisData::const_iterator i = emojisData.cbegin(), e = emojisData.cend(); i != e; ++i) { int len = 1; if (i->code2) { @@ -1564,6 +1574,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org\n\ } else if (i->code >> 16) { if ((i->code >> 16) == 0xFFFF) { // sequence len = textEmojiString(&i.value()).size(); + if (!sequenceOffset) sequenceOffset = index; } else { len = 2; } @@ -1573,20 +1584,20 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org\n\ } bool withPostfix = emojiWithPostfixes.constFind(i->code) != emojiWithPostfixes.cend(); tcpp << "\tnew (toFill++) EmojiData(" << i->x << ", " << i->y << ", 0x" << QString("%1").arg(i->code, 0, 16).toUpper().toUtf8().constData() << "U, 0" << (i->code2 ? ('x' + QString("%1U").arg(i->code2, 0, 16).toUpper()).toUtf8().constData() : "") << ", " << len << (withPostfix ? ", 0xFE0F, 0" : ", 0, 0") << (i->color ? ('x' + QString("%1U").arg(i->color, 0, 16).toUpper()).toUtf8().constData() : "") << ");\n"; + ++index; } tcpp << "};\n\n"; // getter of one symbol emojis - tcpp << "EmojiPtr getEmoji(uint32 code) {\n"; + tcpp << "EmojiPtr emojiGet(uint32 code) {\n"; tcpp << "\tif (!emojis) return 0;\n\n"; tcpp << "\tuint32 highCode = code >> 16;\n"; - uint32 index = 0; EmojisData::const_iterator i = emojisData.cbegin(), e = emojisData.cend(); tcpp << "\tif (!highCode) {\n"; // small codes tcpp << "\t\tswitch (code) {\n"; - for (; i != e; ++i) { // two small + for (index = 0; i != e; ++i) { // two small if (i->code2) break; if (i->code != 169 && i->code != 174) break; @@ -1617,6 +1628,12 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org\n\ tcpp << "\t\treturn 0;\n"; tcpp << "\t}\n\n"; + tcpp << "\tif (highCode == 0xFFFFU) {\n"; // sequences + tcpp << "\t\tstatic const int sequenceOffset = " << sequenceOffset << ";\n\n"; + tcpp << "\t\tuint32 index = (code & 0xFFFFU);\n"; + tcpp << "\t\treturn (index < " << (sizeof(emojiSequences) / sizeof(emojiSequences[0])) << ") ? &emojis[sequenceOffset + index] : 0;\n"; + tcpp << "\t}\n\n"; + tcpp << "\tif (code < 0x" << QString("%1").arg(min2, 0, 16).toUpper().toUtf8().constData() << "U || code > 0x" << QString("%1").arg(max2, 0, 16).toUpper().toUtf8().constData() << "U) return 0;\n\n"; tcpp << "\tswitch (code) {\n"; uint32 minTwoSymbol = 0, maxTwoSymbol = 0; @@ -1634,7 +1651,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org\n\ index++; continue; } - if (i->color) { + if (i->color && ((i->color & 0xFFFF0000U) != 0xFFFF0000U)) { index++; continue; } @@ -1646,7 +1663,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org\n\ tcpp << "}\n\n"; // getter of two symbol emojis - tcpp << "EmojiPtr getEmoji(uint32 code, uint32 code2) {\n"; + tcpp << "EmojiPtr emojiGet(uint32 code, uint32 code2) {\n"; tcpp << "\tif (code < 0x" << QString("%1").arg(minTwoSymbol, 0, 16).toUpper().toUtf8().constData() << "U || code > 0x" << QString("%1").arg(maxTwoSymbol, 0, 16).toUpper().toUtf8().constData() << "U) return 0;\n\n"; tcpp << "\tswitch (code) {\n"; maxTwoSymbol = 0; @@ -1674,7 +1691,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org\n\ tcpp << "}\n\n"; // getter of colored emojis - tcpp << "EmojiPtr getEmoji(EmojiPtr emoji, uint32 color) {\n"; + tcpp << "EmojiPtr emojiGet(EmojiPtr emoji, uint32 color) {\n"; tcpp << "\tif (!emoji || ((emoji->color & 0xFFFF0000U) != 0xFFFF0000U)) return emoji;\n\n"; tcpp << "\tint index = 0;\n"; tcpp << "\tswitch (color) {\n"; @@ -1691,7 +1708,8 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org\n\ for (int j = 0, l = sizeof(emojiSequences) / sizeof(emojiSequences[0]); j < l; ++j) { seqs[j] = QString::fromUtf8(emojiSequences[j]); } - tcpp << "EmojiPtr getEmoji(const QChar *from, const QChar *end) {\n"; + tcpp << "EmojiPtr emojiGet(const QChar *from, const QChar *end) {\n"; + tcpp << "\tstatic const int sequenceOffset = " << sequenceOffset << ";\n\n"; tcpp << "\tif (end < from + 8 || (from + 2)->unicode() != 0x200D || (from + 5)->unicode() != 0x200D) return 0;\n\n"; tcpp << "\tstatic const uint32 "; tcpp << "man = 0x" << QString("%1").arg((uint32(seqs[0].at(0).unicode()) << 16) | uint32(seqs[0].at(1).unicode()), 0, 16).toUpper().toUtf8().constData() << ", "; @@ -1708,36 +1726,36 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org\n\ tcpp << "\t\t\tif (two == man) {\n"; tcpp << "\t\t\t\tif (three == girl) {\n"; - tcpp << "\t\t\t\t\tif (four == girl) return [13];\n"; - tcpp << "\t\t\t\t\tif (four == boy) return [11];\n"; + tcpp << "\t\t\t\t\tif (four == girl) return &emojis[sequenceOffset + 13];\n"; + tcpp << "\t\t\t\t\tif (four == boy) return &emojis[sequenceOffset + 11];\n"; tcpp << "\t\t\t\t} else if (three == boy) {\n"; - tcpp << "\t\t\t\t\tif (four == boy) return [12];\n"; + tcpp << "\t\t\t\t\tif (four == boy) return &emojis[sequenceOffset + 12];\n"; tcpp << "\t\t\t\t}\n"; tcpp << "\t\t\t} else if (two == woman) {\n"; tcpp << "\t\t\t\tif (three == girl) {\n"; - tcpp << "\t\t\t\t\tif (four == girl) return [3];\n"; - tcpp << "\t\t\t\t\tif (four == boy) return [1];\n"; + tcpp << "\t\t\t\t\tif (four == girl) return &emojis[sequenceOffset + 3];\n"; + tcpp << "\t\t\t\t\tif (four == boy) return &emojis[sequenceOffset + 1];\n"; tcpp << "\t\t\t\t} else if (three == boy) {\n"; - tcpp << "\t\t\t\t\tif (four == boy) return [2];\n"; + tcpp << "\t\t\t\t\tif (four == boy) return &emojis[sequenceOffset + 2];\n"; tcpp << "\t\t\t\t}\n"; tcpp << "\t\t\t} else if (two == heart) {\n"; - tcpp << "\t\t\t\tif (three == kiss && four == man) return [17];\n"; + tcpp << "\t\t\t\tif (three == kiss && four == man) return &emojis[sequenceOffset + 17];\n"; tcpp << "\t\t\t}\n"; tcpp << "\t\t} else {\n"; // one == woman tcpp << "\t\t\tif (two == woman) {\n"; tcpp << "\t\t\t\tif (three == girl) {\n"; - tcpp << "\t\t\t\t\tif (four == girl) return [3];\n"; - tcpp << "\t\t\t\t\tif (four == boy) return [1];\n"; + tcpp << "\t\t\t\t\tif (four == girl) return &emojis[sequenceOffset + 8];\n"; + tcpp << "\t\t\t\t\tif (four == boy) return &emojis[sequenceOffset + 6];\n"; tcpp << "\t\t\t\t} else if (three == boy) {\n"; - tcpp << "\t\t\t\t\tif (four == boy) return [2];\n"; + tcpp << "\t\t\t\t\tif (four == boy) return &emojis[sequenceOffset + 7];\n"; tcpp << "\t\t\t\t}\n"; tcpp << "\t\t\t} else if (two == heart) {\n"; - tcpp << "\t\t\t\tif (three == kiss && four == woman) return [16];\n"; + tcpp << "\t\t\t\tif (three == kiss && four == woman) return &emojis[sequenceOffset + 16];\n"; tcpp << "\t\t\t}\n"; tcpp << "\t\t}\n"; tcpp << "\t}\n"; @@ -1745,30 +1763,47 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org\n\ tcpp << "\tif (one == man) {\n"; tcpp << "\t\tif (two == man) {\n"; - tcpp << "\t\t\tif (three == girl) return [10];\n"; - tcpp << "\t\t\tif (three == boy) return [9];\n"; + tcpp << "\t\t\tif (three == girl) return &emojis[sequenceOffset + 10];\n"; + tcpp << "\t\t\tif (three == boy) return &emojis[sequenceOffset + 9];\n"; tcpp << "\t\t} else if (two == woman) {\n"; - tcpp << "\t\t\tif (three == girl) return [0];\n"; + tcpp << "\t\t\tif (three == girl) return &emojis[sequenceOffset + 0];\n"; tcpp << "\t\t} else if (two == heart) {\n"; - tcpp << "\t\t\tif (three == man) return [15];\n"; + tcpp << "\t\t\tif (three == man) return &emojis[sequenceOffset + 15];\n"; tcpp << "\t\t}\n"; tcpp << "\t} else {\n"; // one == woman tcpp << "\t\tif (two == woman) {\n"; - tcpp << "\t\t\tif (three == girl) return [5];\n"; - tcpp << "\t\t\tif (three == boy) return [4];\n"; + tcpp << "\t\t\tif (three == girl) return &emojis[sequenceOffset + 5];\n"; + tcpp << "\t\t\tif (three == boy) return &emojis[sequenceOffset + 4];\n"; tcpp << "\t\t} else if (two == heart) {\n"; - tcpp << "\t\t\tif (three == woman) return [14];\n"; + tcpp << "\t\t\tif (three == woman) return &emojis[sequenceOffset + 14];\n"; tcpp << "\t\t}\n"; tcpp << "\t}\n"; tcpp << "\treturn 0;\n"; tcpp << "}\n\n"; + tcpp << "QString emojiGetSequence(int index) {\n"; + tcpp << "\tstatic QVector sequences;\n"; + tcpp << "\tif (sequences.isEmpty()) {\n"; + tcpp << "\t\tsequences.reserve(" << (sizeof(seqs) / sizeof(seqs[0])) << ");\n\n"; + for (uint32 j = 0; j < (sizeof(emojiSequences) / sizeof(emojiSequences[0])); ++j) { + uint32 len = QByteArray(emojiSequences[j]).size(); + QString str; + str.reserve(4 * len); + for (uint32 k = 0; k < len; ++k) { + str.append(QString("\\x%1").arg(uint32((unsigned char)(emojiSequences[j][k])), 2, 16, QChar('0'))); + } + tcpp << "\t\tsequences.push_back(QString::fromUtf8(\"" << str.toUtf8().constData() << "\"));\n"; + } + tcpp << "\t}\n\n"; + tcpp << "\treturn (index >= 0 && index < sequences.size()) ? sequences.at(index) : QString();\n"; + tcpp << "}\n\n"; + // emoji autoreplace - tcpp << "void findEmoji(const QChar *ch, const QChar *e, const QChar *&newEmojiEnd, uint32 &emojiCode) {\n"; + tcpp << "void emojiFind(const QChar *ch, const QChar *e, const QChar *&newEmojiEnd, uint32 &emojiCode) {\n"; tcpp << "\tswitch (ch->unicode()) {\n"; QString tab("\t"); @@ -1817,6 +1852,19 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org\n\ tcpp << "\t}\n"; tcpp << "}\n\n"; + tcpp << "int emojiPackCount(DBIEmojiTab tab) {\n"; + tcpp << "\tswitch (tab) {\n"; + tcpp << "\t\tcase dbietRecent : return cGetRecentEmojis().size();\n"; + tcpp << "\t\tcase dbietPeople : return " << sizeof(emojiCategory1) / sizeof(emojiCategory1[0]) << ";\n"; + tcpp << "\t\tcase dbietNature : return " << sizeof(emojiCategory2) / sizeof(emojiCategory2[0]) << ";\n"; + tcpp << "\t\tcase dbietFood : return " << sizeof(emojiCategory3) / sizeof(emojiCategory3[0]) << ";\n"; + tcpp << "\t\tcase dbietCelebration: return " << sizeof(emojiCategory4) / sizeof(emojiCategory4[0]) << ";\n"; + tcpp << "\t\tcase dbietActivity : return " << sizeof(emojiCategory5) / sizeof(emojiCategory5[0]) << ";\n"; + tcpp << "\t\tcase dbietTravel : return " << sizeof(emojiCategory6) / sizeof(emojiCategory6[0]) << ";\n"; + tcpp << "\t\tcase dbietObjects : return " << sizeof(emojiCategory7) / sizeof(emojiCategory7[0]) << ";\n"; + tcpp << "\t};\n"; + tcpp << "\treturn 0;\n"; + tcpp << "}\n\n"; tcpp << "EmojiPack emojiPack(DBIEmojiTab tab) {\n"; tcpp << "\tswitch (tab) {\n\n"; writeEmojiCategory(tcpp, emojiCategory1, sizeof(emojiCategory1) / sizeof(emojiCategory1[0]), "People"); @@ -1832,7 +1880,7 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org\n\ tcpp << "\tfor (RecentEmojiPack::const_iterator i = cGetRecentEmojis().cbegin(), e = cGetRecentEmojis().cend(); i != e; ++i) {\n"; tcpp << "\t\tresult.push_back(i->first);\n"; tcpp << "\t}\n"; - tcpp << "\treturn result;"; + tcpp << "\treturn result;\n"; tcpp << "}\n\n"; } QFile cpp(emoji_out); diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 5b978c39b..5759f3706 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -70,9 +70,9 @@ namespace { HistoryItem *hoveredItem = 0, *pressedItem = 0, *hoveredLinkItem = 0, *pressedLinkItem = 0, *contextItem = 0, *mousedItem = 0; - QPixmap *sprite = 0, *emojis = 0; + QPixmap *sprite = 0, *emojis = 0, *emojisLarge = 0; - typedef QMap EmojisMap; + typedef QMap EmojisMap; EmojisMap mainEmojisMap; QMap otherEmojisMap; @@ -1522,11 +1522,15 @@ namespace App { } if (cRetina()) ::sprite->setDevicePixelRatio(cRetinaFactor()); } + emojiInit(); if (!::emojis) { - ::emojis = new QPixmap(st::emojisFile); + ::emojis = new QPixmap(QLatin1String(EName)); if (cRetina()) ::emojis->setDevicePixelRatio(cRetinaFactor()); } - initEmoji(); + if (!::emojisLarge) { + ::emojisLarge = new QPixmap(QLatin1String(EmojiNames[EIndex + 1])); + if (cRetina()) ::emojisLarge->setDevicePixelRatio(cRetinaFactor()); + } } void deinitMedia(bool completely) { @@ -1542,6 +1546,8 @@ namespace App { ::sprite = 0; delete ::emojis; ::emojis = 0; + delete ::emojisLarge; + ::emojisLarge = 0; mainEmojisMap.clear(); otherEmojisMap.clear(); @@ -1610,19 +1616,25 @@ namespace App { return *::emojis; } - const QPixmap &emojiSingle(const EmojiData *emoji, int32 fontHeight) { + const QPixmap &emojisLarge() { + return *::emojisLarge; + } + + const QPixmap &emojiSingle(EmojiPtr emoji, int32 fontHeight) { EmojisMap *map = &(fontHeight == st::taDefFlat.font->height ? mainEmojisMap : otherEmojisMap[fontHeight]); - EmojisMap::const_iterator i = map->constFind(emoji->code); + EmojisMap::const_iterator i = map->constFind(emojiKey(emoji)); if (i == map->cend()) { - QImage img(st::emojiImgSize + st::emojiPadding * cIntRetinaFactor() * 2, fontHeight * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + QImage img(ESize + st::emojiPadding * cIntRetinaFactor() * 2, fontHeight * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); if (cRetina()) img.setDevicePixelRatio(cRetinaFactor()); { QPainter p(&img); + QPainter::CompositionMode m = p.compositionMode(); p.setCompositionMode(QPainter::CompositionMode_Source); p.fillRect(0, 0, img.width(), img.height(), Qt::transparent); - p.drawPixmap(QPoint(st::emojiPadding * cIntRetinaFactor(), (fontHeight * cIntRetinaFactor() - st::emojiImgSize) / 2), App::emojis(), QRect(emoji->x, emoji->y, st::emojiImgSize, st::emojiImgSize)); + p.setCompositionMode(m); + emojiDraw(p, emoji, st::emojiPadding * cIntRetinaFactor(), (fontHeight * cIntRetinaFactor() - ESize) / 2); } - i = map->insert(emoji->code, QPixmap::fromImage(img, Qt::ColorOnly)); + i = map->insert(emojiKey(emoji), QPixmap::fromImage(img, Qt::ColorOnly)); } return i.value(); } diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index 1f32f206f..8d2bd91f9 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -156,7 +156,8 @@ namespace App { const QPixmap &sprite(); const QPixmap &emojis(); - const QPixmap &emojiSingle(const EmojiData *emoji, int32 fontHeight); + const QPixmap &emojisLarge(); + const QPixmap &emojiSingle(EmojiPtr emoji, int32 fontHeight); void initMedia(); void deinitMedia(bool completely = true); diff --git a/Telegram/SourceFiles/art/emoji.png b/Telegram/SourceFiles/art/emoji.png deleted file mode 100644 index a129897b1..000000000 Binary files a/Telegram/SourceFiles/art/emoji.png and /dev/null differ diff --git a/Telegram/SourceFiles/art/emoji.webp b/Telegram/SourceFiles/art/emoji.webp new file mode 100644 index 000000000..f1f51e2dc Binary files /dev/null and b/Telegram/SourceFiles/art/emoji.webp differ diff --git a/Telegram/SourceFiles/art/emoji_125x.png b/Telegram/SourceFiles/art/emoji_125x.png deleted file mode 100644 index ec55f096e..000000000 Binary files a/Telegram/SourceFiles/art/emoji_125x.png and /dev/null differ diff --git a/Telegram/SourceFiles/art/emoji_125x.webp b/Telegram/SourceFiles/art/emoji_125x.webp new file mode 100644 index 000000000..3b19b139d Binary files /dev/null and b/Telegram/SourceFiles/art/emoji_125x.webp differ diff --git a/Telegram/SourceFiles/art/emoji_150x.png b/Telegram/SourceFiles/art/emoji_150x.png deleted file mode 100644 index 15e723d15..000000000 Binary files a/Telegram/SourceFiles/art/emoji_150x.png and /dev/null differ diff --git a/Telegram/SourceFiles/art/emoji_150x.webp b/Telegram/SourceFiles/art/emoji_150x.webp new file mode 100644 index 000000000..d40e59738 Binary files /dev/null and b/Telegram/SourceFiles/art/emoji_150x.webp differ diff --git a/Telegram/SourceFiles/art/emoji_200x.png b/Telegram/SourceFiles/art/emoji_200x.png deleted file mode 100644 index b5c14b646..000000000 Binary files a/Telegram/SourceFiles/art/emoji_200x.png and /dev/null differ diff --git a/Telegram/SourceFiles/art/emoji_200x.webp b/Telegram/SourceFiles/art/emoji_200x.webp new file mode 100644 index 000000000..c7a998a49 Binary files /dev/null and b/Telegram/SourceFiles/art/emoji_200x.webp differ diff --git a/Telegram/SourceFiles/art/emoji_250x.webp b/Telegram/SourceFiles/art/emoji_250x.webp new file mode 100644 index 000000000..f09e6b06e Binary files /dev/null and b/Telegram/SourceFiles/art/emoji_250x.webp differ diff --git a/Telegram/SourceFiles/art/emojisprite_0.png b/Telegram/SourceFiles/art/emojisprite_0.png deleted file mode 100644 index 1adfcc97d..000000000 Binary files a/Telegram/SourceFiles/art/emojisprite_0.png and /dev/null differ diff --git a/Telegram/SourceFiles/art/emojisprite_1.png b/Telegram/SourceFiles/art/emojisprite_1.png deleted file mode 100644 index 5db1e9e7b..000000000 Binary files a/Telegram/SourceFiles/art/emojisprite_1.png and /dev/null differ diff --git a/Telegram/SourceFiles/art/emojisprite_2.png b/Telegram/SourceFiles/art/emojisprite_2.png deleted file mode 100644 index 63e106175..000000000 Binary files a/Telegram/SourceFiles/art/emojisprite_2.png and /dev/null differ diff --git a/Telegram/SourceFiles/art/emojisprite_3.png b/Telegram/SourceFiles/art/emojisprite_3.png deleted file mode 100644 index 5f259ecbd..000000000 Binary files a/Telegram/SourceFiles/art/emojisprite_3.png and /dev/null differ diff --git a/Telegram/SourceFiles/art/emojisprite_4.png b/Telegram/SourceFiles/art/emojisprite_4.png deleted file mode 100644 index ec146b27b..000000000 Binary files a/Telegram/SourceFiles/art/emojisprite_4.png and /dev/null differ diff --git a/Telegram/SourceFiles/art/sprite.png b/Telegram/SourceFiles/art/sprite.png index 86fb78fda..39ef10656 100644 Binary files a/Telegram/SourceFiles/art/sprite.png and b/Telegram/SourceFiles/art/sprite.png differ diff --git a/Telegram/SourceFiles/art/sprite_200x.png b/Telegram/SourceFiles/art/sprite_200x.png index e2d45ef52..5f8e42002 100644 Binary files a/Telegram/SourceFiles/art/sprite_200x.png and b/Telegram/SourceFiles/art/sprite_200x.png differ diff --git a/Telegram/SourceFiles/boxes/emojibox.cpp b/Telegram/SourceFiles/boxes/emojibox.cpp index 85b8b574d..c38fe9068 100644 --- a/Telegram/SourceFiles/boxes/emojibox.cpp +++ b/Telegram/SourceFiles/boxes/emojibox.cpp @@ -84,7 +84,7 @@ void EmojiBox::fillBlocks() { BlockRow currentRow; currentRow.reserve(replacesInRow); for (uint32 i = 0; i < replacesCount; ++i) { - Block block(getEmoji(replaces[i].code), QString::fromUtf8(replaces[i].replace)); + Block block(emojiGet(replaces[i].code), QString::fromUtf8(replaces[i].replace)); currentRow.push_back(block); if (uint32(currentRow.size()) == replacesInRow) { _blocks.push_back(currentRow); @@ -125,8 +125,7 @@ void EmojiBox::paintEvent(QPaintEvent *e) { int32 rowSize = i->size(), left = (width() - rowSize * st::emojiReplaceWidth) / 2; for (BlockRow::const_iterator j = i->cbegin(), en = i->cend(); j != en; ++j) { if (j->emoji) { - QPoint pos(left + (st::emojiReplaceWidth - st::emojiSize) / 2, top + (st::emojiReplaceHeight - _blockHeight) / 2); - p.drawPixmap(pos, App::emojis(), QRect(j->emoji->x, j->emoji->y, st::emojiImgSize, st::emojiImgSize)); + emojiDraw(p, j->emoji, left + (st::emojiReplaceWidth - st::emojiSize) / 2, top + (st::emojiReplaceHeight - _blockHeight) / 2); } QRect trect(left, top + (st::emojiReplaceHeight + _blockHeight) / 2 - st::emojiTextFont->height, st::emojiReplaceWidth, st::emojiTextFont->height); p.drawText(trect, j->text, QTextOption(Qt::AlignHCenter | Qt::AlignTop)); diff --git a/Telegram/SourceFiles/dropdown.cpp b/Telegram/SourceFiles/dropdown.cpp index 4b8570c7a..f6f37e024 100644 --- a/Telegram/SourceFiles/dropdown.cpp +++ b/Telegram/SourceFiles/dropdown.cpp @@ -442,225 +442,208 @@ bool DragArea::animStep(float64 ms) { return res; } -EmojiPanInner::EmojiPanInner(QWidget *parent) : QWidget(parent), _tab(cEmojiTab()), _selected(-1), _xSelected(-1), _pressedSel(-1), _xPressedSel(-1) { - resize(EmojiPadPerRow * st::emojiPanSize.width(), EmojiPadRowsPerPage * st::emojiPanSize.height() - st::emojiPanSub); +EmojiColorPicker::EmojiColorPicker(QWidget *parent) : TWidget(parent), +_ignoreShow(false), _selected(-1), _pressedSel(-1), _hiding(false), a_opacity(0), _shadow(st::dropdownDef.shadow) { + memset(_variants, 0, sizeof(_variants)); + memset(_hovers, 0, sizeof(_hovers)); + setMouseTracking(true); setFocusPolicy(Qt::NoFocus); - _saveConfigTimer.setSingleShot(true); - connect(&_saveConfigTimer, SIGNAL(timeout()), this, SLOT(onSaveConfig())); + int32 w = st::emojiPanSize.width() * (EmojiColorsCount + 1) + 4 * st::emojiColorsPadding + st::emojiColorsSep + st::dropdownDef.shadow.pxWidth() * 2; + int32 h = 2 * st::emojiColorsPadding + st::emojiPanSize.height() + st::dropdownDef.shadow.pxHeight() * 2; + resize(w, h); + + _hideTimer.setSingleShot(true); + connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideStart())); } -void EmojiPanInner::paintEvent(QPaintEvent *e) { - QPainter p(this); +void EmojiColorPicker::showEmoji(uint32 code) { + EmojiPtr e = emojiGet(code); + if (!e || e == TwoSymbolEmoji || !e->color) { + return; + } + _ignoreShow = false; - QRect r = e ? e->rect() : rect(); + _variants[0] = e; + _variants[1] = emojiGet(e, 0xD83CDFFB); + _variants[2] = emojiGet(e, 0xD83CDFFC); + _variants[3] = emojiGet(e, 0xD83CDFFD); + _variants[4] = emojiGet(e, 0xD83CDFFE); + _variants[5] = emojiGet(e, 0xD83CDFFF); - if (_tab == dbietStickers) { - int32 size = _stickers.size(); - float64 stickerWidth = width() / float64(StickerPadPerRow); - int32 rows = (size / StickerPadPerRow) + ((size % StickerPadPerRow) ? 1 : 0), stickerSize = int32(stickerWidth); - int32 fromrow = qMax(qFloor(r.top() / stickerSize), 0), torow = qMin(qCeil(r.bottom() / stickerSize) + 1, rows); - for (int32 i = fromrow; i < torow; ++i) { - for (int32 j = 0; j < StickerPadPerRow; ++j) { - int32 index = i * StickerPadPerRow + j; - if (index >= size) break; + if (!_cache.isNull()) _cache = QPixmap(); + showStart(); +} - float64 hover = _hovers[index]; +void EmojiColorPicker::paintEvent(QPaintEvent *e) { + Painter p(this); - QPoint pos(qRound(j * stickerWidth), i * stickerSize); - if (hover > 0) { - p.setOpacity(hover); - p.setBrush(st::emojiPanHover->b); - p.setPen(Qt::NoPen); - p.drawRoundedRect(QRect(pos, QSize(stickerSize, stickerSize)), st::stickerPanRound, st::stickerPanRound); - p.setOpacity(1); - } + if (!_cache.isNull()) { + p.setOpacity(a_opacity.current()); + } - DocumentData *sticker = _stickers[index]; - bool already = !sticker->already().isEmpty(), hasdata = !sticker->data.isEmpty(); - if (!sticker->loader && sticker->status != FileFailed && !already && !hasdata) { - sticker->save(QString()); - } - if (sticker->sticker->isNull() && (already || hasdata)) { - if (already) { - sticker->sticker = ImagePtr(sticker->already()); - } else { - sticker->sticker = ImagePtr(sticker->data); - } - } + int32 w = st::dropdownDef.shadow.pxWidth(), h = st::dropdownDef.shadow.pxHeight(); + QRect r = QRect(w, h, width() - 2 * w, height() - 2 * h); + _shadow.paint(p, r); - float64 coef = qMin((stickerWidth - st::stickerPanPadding * 2) / float64(sticker->dimensions.width()), (stickerSize - st::stickerPanPadding * 2) / float64(sticker->dimensions.height())); - if (coef > 1) coef = 1; - int32 w = qRound(coef * sticker->dimensions.width()), h = qRound(coef * sticker->dimensions.height()); - if (w < 1) w = 1; - if (h < 1) h = 1; - QPoint ppos = pos + QPoint((stickerSize - w) / 2, (stickerSize - h) / 2); - if (sticker->sticker->isNull()) { - p.drawPixmap(ppos, sticker->thumb->pix(w, h)); - } else { - p.drawPixmap(ppos, sticker->sticker->pix(w, h)); - } + if (_cache.isNull()) { + p.fillRect(r, st::white->b); + p.translate(w, h); - if (hover > 0 && _isUserGen[index]) { - float64 xHover = _hovers[_stickers.size() + index]; + p.fillRect(2 * st::emojiColorsPadding + st::emojiPanSize.width(), st::emojiColorsPadding, st::emojiColorsSep, r.height() - st::emojiColorsPadding * 2, st::emojiColorsSepColor->b); - QPoint xPos = pos + QPoint(stickerWidth - st::stickerPanDelete.pxWidth(), 0); - p.setOpacity(hover * (xHover + (1 - xHover) * st::stickerPanDeleteOpacity)); - p.drawPixmap(xPos, App::sprite(), st::stickerPanDelete); - p.setOpacity(1); - } - } + if (!_variants[0]) return; + for (int i = 0; i < EmojiColorsCount + 1; ++i) { + drawVariant(p, i); } } else { - int32 size = _emojis.size(); - int32 rows = (size / EmojiPadPerRow) + ((size % EmojiPadPerRow) ? 1 : 0); - int32 fromrow = qMax(qFloor(r.top() / st::emojiPanSize.height()), 0), torow = qMin(qCeil(r.bottom() / st::emojiPanSize.height()) + 1, rows); - for (int32 i = fromrow; i < torow; ++i) { - for (int32 j = 0; j < EmojiPadPerRow; ++j) { - int32 index = i * EmojiPadPerRow + j; - if (index >= size) break; - - float64 hover = _hovers[index]; - - QPoint w(j * st::emojiPanSize.width(), i * st::emojiPanSize.height()); - if (hover > 0) { - p.setOpacity(hover); - p.setBrush(st::emojiPanHover->b); - p.setPen(Qt::NoPen); - p.drawRoundedRect(QRect(w, st::emojiPanSize), st::emojiPanRound, st::emojiPanRound); - p.setOpacity(1); - } - QRect r(_emojis[index]->x, _emojis[index]->y, st::emojiImgSize, st::emojiImgSize); - p.drawPixmap(w + QPoint((st::emojiPanSize.width() - st::emojiSize) / 2, (st::emojiPanSize.height() - st::emojiSize) / 2), App::emojis(), r); - } - } + p.drawPixmap(r.left(), r.top(), _cache); } + } -void EmojiPanInner::mousePressEvent(QMouseEvent *e) { +void EmojiColorPicker::enterEvent(QEvent *e) { + _hideTimer.stop(); + if (_hiding) showStart(); + TWidget::enterEvent(e); +} + +void EmojiColorPicker::leaveEvent(QEvent *e) { + TWidget::leaveEvent(e); +} + +void EmojiColorPicker::mousePressEvent(QMouseEvent *e) { _lastMousePos = e->globalPos(); updateSelected(); _pressedSel = _selected; - _xPressedSel = _xSelected; } -void EmojiPanInner::mouseReleaseEvent(QMouseEvent *e) { - _lastMousePos = e->globalPos(); - updateSelected(); - if (_xSelected == _xPressedSel && _xSelected >= 0 && _tab == dbietStickers) { - RecentStickerPack recent(cRecentStickers()); - DocumentData *sticker = _stickers.at(_xSelected - _stickers.size()); - for (int32 i = 0, l = recent.size(); i < l; ++i) { - if (recent.at(i).first == sticker) { - recent.removeAt(i); - cSetRecentStickers(recent); - Local::writeRecentStickers(); - showEmojiPack(dbietStickers); - updateSelected(); - break; - } - } - } else if (_selected == _pressedSel && _selected >= 0) { - if (_tab == dbietStickers) { - if (_selected < _stickers.size()) { - emit stickerSelected(_stickers[_selected]); - } - } else if (_selected < _emojis.size()) { - EmojiPtr emoji(_emojis[_selected]); - RecentEmojiPack recent(cGetRecentEmojis()); - RecentEmojiPack::iterator i = recent.begin(), e = recent.end(); - for (; i != e; ++i) { - if (i->first == emoji) { - ++i->second; - if (i->second > 0x8000) { - for (RecentEmojiPack::iterator j = recent.begin(); j != e; ++j) { - if (j->second > 1) { - j->second /= 2; - } else { - j->second = 1; - } - } - } - for (; i != recent.begin(); --i) { - if ((i - 1)->second > i->second) { - break; - } - qSwap(*i, *(i - 1)); - } - break; - } - } - if (i == e) { - while (recent.size() >= EmojiPadPerRow * EmojiPadRowsPerPage) recent.pop_back(); - recent.push_back(qMakePair(emoji, 1)); - for (i = recent.end() - 1; i != recent.begin(); --i) { - if ((i - 1)->second > i->second) { - break; - } - qSwap(*i, *(i - 1)); - } - } - cSetRecentEmojis(recent); - _saveConfigTimer.start(SaveRecentEmojisTimeout); +void EmojiColorPicker::mouseReleaseEvent(QMouseEvent *e) { + _lastMousePos = e ? e->globalPos() : QCursor::pos(); + int32 pressed = _pressedSel; + _pressedSel = -1; - emit emojiSelected(emoji); + updateSelected(); + if (_selected >= 0 && (pressed < 0 || _selected == pressed)) { + emit emojiSelected(_variants[_selected]); + } + _ignoreShow = true; + hideStart(); +} + +void EmojiColorPicker::mouseMoveEvent(QMouseEvent *e) { + _lastMousePos = e ? e->globalPos() : QCursor::pos(); + updateSelected(); +} + +bool EmojiColorPicker::animStep(float64 ms) { + bool res1 = true, res2 = true; + if (!_cache.isNull()) { + float64 dt = ms / st::dropdownDef.duration; + if (dt >= 1) { + a_opacity.finish(); + _cache = QPixmap(); + if (_hiding) { + hide(); + emit hidden(); + } else { + _lastMousePos = QCursor::pos(); + updateSelected(); + } + res1 = false; + } else { + a_opacity.update(dt, anim::linear); } } + if (!_emojiAnimations.isEmpty()) { + uint64 now = getms(); + for (EmojiAnimations::iterator i = _emojiAnimations.begin(); i != _emojiAnimations.end();) { + int index = qAbs(i.key()) - 1; + float64 dt = float64(now - i.value()) / st::emojiPanDuration; + if (dt >= 1) { + _hovers[index] = (i.key() > 0) ? 1 : 0; + i = _emojiAnimations.erase(i); + } else { + _hovers[index] = (i.key() > 0) ? dt : (1 - dt); + ++i; + } + } + res2 = !_emojiAnimations.isEmpty(); + } + update(); + return res1 || res2; +} + +void EmojiColorPicker::hideStart(bool fast) { + if (fast) { + clearSelection(true); + if (animating()) anim::stop(this); + a_opacity = anim::fvalue(0); + _cache = QPixmap(); + hide(); + emit hidden(); + } else { + if (_cache.isNull()) { + int32 w = st::dropdownDef.shadow.pxWidth(), h = st::dropdownDef.shadow.pxHeight(); + _cache = myGrab(this, QRect(w, h, width() - 2 * w, height() - 2 * h)); + clearSelection(true); + } + _hiding = true; + a_opacity.start(0); + anim::start(this); + } } -void EmojiPanInner::onSaveConfig() { - Local::writeUserSettings(); +void EmojiColorPicker::showStart() { + if (_ignoreShow) return; + + _hiding = false; + if (!isHidden() && a_opacity.current() == 1) { + if (animating()) { + anim::stop(this); + _cache = QPixmap(); + } + return; + } + if (_cache.isNull()) { + int32 w = st::dropdownDef.shadow.pxWidth(), h = st::dropdownDef.shadow.pxHeight(); + _cache = myGrab(this, QRect(w, h, width() - 2 * w, height() - 2 * h)); + clearSelection(true); + } + show(); + a_opacity.start(1); + anim::start(this); } -void EmojiPanInner::mouseMoveEvent(QMouseEvent *e) { - _lastMousePos = e->globalPos(); - updateSelected(); -} - -void EmojiPanInner::leaveEvent(QEvent *e) { - clearSelection(); -} - -void EmojiPanInner::clearSelection(bool fast) { +void EmojiColorPicker::clearSelection(bool fast) { + _pressedSel = -1; _lastMousePos = mapToGlobal(QPoint(-10, -10)); if (fast) { - if (_tab == dbietStickers) { - _hovers = QVector(_stickers.size() * 2, 0); - } else { - _hovers = QVector(_emojis.size(), 0); - } + _selected = -1; + memset(_hovers, 0, sizeof(_hovers)); _emojiAnimations.clear(); - _selected = _pressedSel = _xSelected = _xPressedSel = -1; - anim::stop(this); } else { updateSelected(); } } -void EmojiPanInner::updateSelected() { - int32 selIndex = -1, xSelIndex = -1; +void EmojiColorPicker::updateSelected() { + int32 selIndex = -1; QPoint p(mapFromGlobal(_lastMousePos)); - if (_tab == dbietStickers) { - float64 stickerWidth = width() / float64(StickerPadPerRow); - int32 stickerSize = int32(stickerWidth); - if (p.x() >= 0 && p.y() >= 0 && p.x() < StickerPadPerRow * stickerWidth) { - selIndex = qFloor(p.y() / stickerSize) * StickerPadPerRow + qFloor(p.x() / stickerWidth); - if (selIndex >= _stickers.size()) { - selIndex = -1; - } else { - int32 inx = p.x() - (selIndex % StickerPadPerRow) * stickerWidth, iny = p.y() - ((selIndex / StickerPadPerRow) * stickerSize); - if (inx >= stickerWidth - st::stickerPanDelete.pxWidth() && iny < st::stickerPanDelete.pxHeight()) { - xSelIndex = _stickers.size() + selIndex; - } + int32 y = p.y() - st::dropdownDef.shadow.pxHeight() - st::emojiColorsPadding; + if (y >= 0 && y < st::emojiPanSize.height()) { + int32 x = p.x() - st::dropdownDef.shadow.pxWidth() - st::emojiColorsPadding; + if (x >= 0 && x < st::emojiPanSize.width()) { + selIndex = 0; + } else { + x -= st::emojiPanSize.width() + 2 * st::emojiColorsPadding + st::emojiColorsSep; + if (x >= 0 && x < st::emojiPanSize.width() * EmojiColorsCount) { + selIndex = (x / st::emojiPanSize.width()) + 1; } } - } else if (p.x() >= 0 && p.y() >= 0 && p.x() < EmojiPadPerRow * st::emojiPanSize.width()) { - selIndex = qFloor(p.y() / st::emojiPanSize.height()) * EmojiPadPerRow + qFloor(p.x() / st::emojiPanSize.width()); - if (selIndex >= _emojis.size()) { - selIndex = -1; - } } + bool startanim = false; if (selIndex != _selected) { if (_selected >= 0) { @@ -680,35 +663,611 @@ void EmojiPanInner::updateSelected() { } setCursor((_selected >= 0) ? style::cur_pointer : style::cur_default); } - if (xSelIndex != _xSelected) { - if (_xSelected >= 0) { - _emojiAnimations.remove(_xSelected + 1); - if (_emojiAnimations.find(-_xSelected - 1) == _emojiAnimations.end()) { - if (_emojiAnimations.isEmpty()) startanim = true; - _emojiAnimations.insert(-_xSelected - 1, getms()); + if (startanim && !animating()) anim::start(this); +} + +void EmojiColorPicker::drawVariant(Painter &p, int variant) { + float64 hover = _hovers[variant]; + + QPoint w(st::emojiColorsPadding + variant * st::emojiPanSize.width() + (variant ? 2 * st::emojiColorsPadding + st::emojiColorsSep : 0), st::emojiColorsPadding); + if (hover > 0) { + p.setOpacity(hover); + p.setBrush(st::emojiPanHover->b); + p.setPen(Qt::NoPen); + p.drawRoundedRect(QRect(w, st::emojiPanSize), st::emojiPanRound, st::emojiPanRound); + p.setOpacity(1); + } + int esize = EmojiSizes[EIndex + 1]; + p.drawPixmapLeft(w.x() + (st::emojiPanSize.width() - (esize / cIntRetinaFactor())) / 2, w.y() + (st::emojiPanSize.height() - (esize / cIntRetinaFactor())) / 2, width(), App::emojisLarge(), QRect(_variants[variant]->x * esize, _variants[variant]->y * esize, esize, esize)); + +} + +EmojiPanInner::EmojiPanInner(QWidget *parent) : TWidget(parent), _top(0), _selected(-1), _pressedSel(-1), _pickerSel(-1), _picker(this) { + resize(EmojiPadPerRow * st::emojiPanSize.width(), countHeight()); + setMouseTracking(true); + setFocusPolicy(Qt::NoFocus); + + _picker.hide(); + + _esize = EmojiSizes[EIndex + 1]; + + int sum = 0; + for (int i = 0; i < emojiTabCount; ++i) { + sum += (_counts[i] = emojiPackCount(emojiTabAtIndex(i))); + _hovers[i] = QVector(_counts[i], 0); + } + _count = sum; + + _saveConfigTimer.setSingleShot(true); + connect(&_saveConfigTimer, SIGNAL(timeout()), this, SLOT(onSaveConfig())); + + _showPickerTimer.setSingleShot(true); + connect(&_showPickerTimer, SIGNAL(timeout()), this, SLOT(onShowPicker())); + connect(&_picker, SIGNAL(emojiSelected(EmojiPtr)), this, SLOT(onColorSelected(EmojiPtr))); + connect(&_picker, SIGNAL(hidden()), this, SLOT(onPickerHidden())); +} + +void EmojiPanInner::setScrollTop(int top) { + if (top == _top) return; + + QRegion upd = QRect(0, _top, width(), st::emojiPanHeader); + _top = top; + upd += QRect(0, _top, width(), st::emojiPanHeader); + repaint(upd); + updateSelected(); +} + +int EmojiPanInner::countHeight() { + int result = 0; + for (int i = 0; i < emojiTabCount; ++i) { + int cnt = emojiPackCount(emojiTabAtIndex(i)), rows = (cnt / EmojiPadPerRow) + ((cnt % EmojiPadPerRow) ? 1 : 0); + result += st::emojiPanHeader + rows * st::emojiPanSize.height(); + } + + result += st::emojiPanHeader; + int32 cnt = cRecentStickers().size(), rows = (cnt / StickerPadPerRow) + ((cnt % StickerPadPerRow) ? 1 : 0); + _stickerWidth = (EmojiPadPerRow * st::emojiPanSize.width()) / float64(StickerPadPerRow); + _stickerSize = int32(_stickerWidth); + result += rows * _stickerSize; + return result; +} + +void EmojiPanInner::paintEvent(QPaintEvent *e) { + Painter p(this); + QRect r = e ? e->rect() : rect(); + if (r != rect()) { + p.setClipRect(r); + } + p.fillRect(r, st::white->b); + + float64 stickerWidth = width() / float64(StickerPadPerRow); + int32 stickerSize = int32(stickerWidth); + + int32 fullh = 0; + for (int c = 0; c < emojiTabCount; ++c) { + int32 size = _counts[c], rows = (size / EmojiPadPerRow) + ((size % EmojiPadPerRow) ? 1 : 0); + fullh += st::emojiPanHeader + (rows * st::emojiPanSize.height()); + } + int32 ssize = _stickers.size(), srows = (ssize / StickerPadPerRow) + ((ssize % StickerPadPerRow) ? 1 : 0); + fullh += st::emojiPanHeader + ssize * stickerSize; + int32 downfrom = 0, uptill = (_top * _stickers.size()) / fullh; + if (uptill >= _stickers.size()) uptill = _stickers.size(); + if (uptill > downfrom + StickerPadPerRow * 4) { + downfrom = uptill - StickerPadPerRow * 4; + } + for (int index = downfrom; index < uptill; ++index) { // preload stickers + DocumentData *sticker = _stickers[index]; + bool already = !sticker->already().isEmpty(), hasdata = !sticker->data.isEmpty(); + if (!sticker->loader && sticker->status != FileFailed && !already && !hasdata) { + sticker->save(QString()); + } + if (sticker->sticker->isNull() && (already || hasdata)) { + if (already) { + sticker->sticker = ImagePtr(sticker->already()); + } else { + sticker->sticker = ImagePtr(sticker->data); } } - _xSelected = xSelIndex; - if (_xSelected >= 0) { - _emojiAnimations.remove(-_xSelected - 1); - if (_emojiAnimations.find(_xSelected + 1) == _emojiAnimations.end()) { - if (_emojiAnimations.isEmpty()) startanim = true; - _emojiAnimations.insert(_xSelected + 1, getms()); + + float64 coef = qMin((stickerWidth - st::stickerPanPadding * 2) / float64(sticker->dimensions.width()), (stickerSize - st::stickerPanPadding * 2) / float64(sticker->dimensions.height())); + if (coef > 1) coef = 1; + int32 w = qRound(coef * sticker->dimensions.width()), h = qRound(coef * sticker->dimensions.height()); + if (w < 1) w = 1; + if (h < 1) h = 1; + if (!sticker->sticker->isNull()) sticker->sticker->pix(w, h); + } + + int32 y, tilly = 0; + for (int c = 0; c < emojiTabCount; ++c) { + y = tilly; + int32 size = _counts[c]; + int32 rows = (size / EmojiPadPerRow) + ((size % EmojiPadPerRow) ? 1 : 0); + tilly = y + st::emojiPanHeader + (rows * st::emojiPanSize.height()); + if (r.top() >= tilly) continue; + + y += st::emojiPanHeader; + + if (r.bottom() <= y) { + p.setFont(st::emojiPanHeaderFont->f); + p.setPen(st::emojiPanHeaderColor->p); + p.drawTextLeft(st::emojiPanHeaderLeft, qMax(y - int(st::emojiPanHeader), _top) + st::emojiPanHeaderTop, width(), lang(LangKey(lng_emoji_category0 + c))); + break; + } + + if (_emojis[c].isEmpty()) { + _emojis[c] = emojiPack(emojiTabAtIndex(c)); + if (emojiTabAtIndex(c) != dbietRecent) { + for (EmojiPack::iterator i = _emojis[c].begin(), e = _emojis[c].end(); i != e; ++i) { + if ((*i)->color) { + EmojiColorVariants::const_iterator j = cEmojiVariants().constFind((*i)->code); + if (j != cEmojiVariants().cend()) { + EmojiPtr replace = emojiFromKey(j.value()); + if (replace) { + if (replace != TwoSymbolEmoji && replace->code == (*i)->code && replace->code2 == (*i)->code2) { + *i = replace; + } + } + } + } + } + } + } + + int32 fromrow = (r.top() <= y) ? 0 : qMax(qFloor((r.top() - y) / st::emojiPanSize.height()), 0), torow = qMin(qCeil((r.bottom() - y) / st::emojiPanSize.height()) + 1, rows); + for (int32 i = fromrow; i < torow; ++i) { + for (int32 j = 0; j < EmojiPadPerRow; ++j) { + int32 index = i * EmojiPadPerRow + j; + if (index >= size) break; + + float64 hover = (!_picker.isHidden() && c * emojiTabShift + index == _pickerSel) ? 1 : _hovers[c][index]; + + QPoint w(j * st::emojiPanSize.width(), y + i * st::emojiPanSize.height()); + if (hover > 0) { + p.setOpacity(hover); + p.setBrush(st::emojiPanHover->b); + p.setPen(Qt::NoPen); + p.drawRoundedRect(QRect(w, st::emojiPanSize), st::emojiPanRound, st::emojiPanRound); + p.setOpacity(1); + } + p.drawPixmapLeft(w.x() + (st::emojiPanSize.width() - (_esize / cIntRetinaFactor())) / 2, w.y() + (st::emojiPanSize.height() - (_esize / cIntRetinaFactor())) / 2, width(), App::emojisLarge(), QRect(_emojis[c][index]->x * _esize, _emojis[c][index]->y * _esize, _esize, _esize)); + } + } + + if (y - int(st::emojiPanHeader) < _top) { + p.fillRect(QRect(0, qMin(_top, tilly - int(st::emojiPanHeader)), width(), st::emojiPanHeader), st::emojiPanHeaderBg->b); + } + p.setFont(st::emojiPanHeaderFont->f); + p.setPen(st::emojiPanHeaderColor->p); + p.drawTextLeft(st::emojiPanHeaderLeft, qMin(qMax(y - int(st::emojiPanHeader), _top), tilly - int(st::emojiPanHeader)) + st::emojiPanHeaderTop, width(), lang(LangKey(lng_emoji_category0 + c))); + } + + y = tilly; + int32 size = _stickers.size(), rows = (size / StickerPadPerRow) + ((size % StickerPadPerRow) ? 1 : 0); + tilly = y + st::emojiPanHeader + (rows * stickerSize); + if (r.top() >= tilly) return; + + y += st::emojiPanHeader; + + if (r.bottom() <= y) { + p.setFont(st::emojiPanHeaderFont->f); + p.setPen(st::emojiPanHeaderColor->p); + p.drawTextLeft(st::emojiPanHeaderLeft, qMax(y - int(st::emojiPanHeader), _top) + st::emojiPanHeaderTop, width(), lang(LangKey(lng_emoji_category0 + emojiTabCount))); + return; + } + + int32 fromrow = (r.top() <= y) ? 0 : qMax(qFloor((r.top() - y) / stickerSize), 0), torow = qMin(qCeil((r.bottom() - y) / stickerSize) + 1, rows); + for (int32 i = fromrow; i < torow; ++i) { + for (int32 j = 0; j < StickerPadPerRow; ++j) { + int32 index = i * StickerPadPerRow + j; + if (index >= size) break; + + float64 hover = _hovers[emojiTabCount][index]; + + QPoint pos(qRound(j * stickerWidth), y + i * stickerSize); + if (hover > 0) { + p.setOpacity(hover); + p.setBrush(st::emojiPanHover->b); + p.setPen(Qt::NoPen); + p.drawRoundedRect(QRect(pos, QSize(stickerSize, stickerSize)), st::stickerPanRound, st::stickerPanRound); + p.setOpacity(1); + } + + DocumentData *sticker = _stickers[index]; + bool already = !sticker->already().isEmpty(), hasdata = !sticker->data.isEmpty(); + if (!sticker->loader && sticker->status != FileFailed && !already && !hasdata) { + sticker->save(QString()); + } + if (sticker->sticker->isNull() && (already || hasdata)) { + if (already) { + sticker->sticker = ImagePtr(sticker->already()); + } else { + sticker->sticker = ImagePtr(sticker->data); + } + } + + float64 coef = qMin((stickerWidth - st::stickerPanPadding * 2) / float64(sticker->dimensions.width()), (stickerSize - st::stickerPanPadding * 2) / float64(sticker->dimensions.height())); + if (coef > 1) coef = 1; + int32 w = qRound(coef * sticker->dimensions.width()), h = qRound(coef * sticker->dimensions.height()); + if (w < 1) w = 1; + if (h < 1) h = 1; + QPoint ppos = pos + QPoint((stickerSize - w) / 2, (stickerSize - h) / 2); + if (sticker->sticker->isNull()) { + p.drawPixmap(ppos, sticker->thumb->pix(w, h)); + } else { + p.drawPixmap(ppos, sticker->sticker->pix(w, h)); + } + + if (hover > 0 && _isUserGen[index]) { + float64 xHover = _hovers[emojiTabCount][_stickers.size() + index]; + + QPoint xPos = pos + QPoint(stickerWidth - st::stickerPanDelete.pxWidth(), 0); + p.setOpacity(hover * (xHover + (1 - xHover) * st::stickerPanDeleteOpacity)); + p.drawPixmap(xPos, App::sprite(), st::stickerPanDelete); + p.setOpacity(1); } } } + + if (y - int(st::emojiPanHeader) < _top) { + p.fillRect(QRect(0, qMin(_top, tilly - int(st::emojiPanHeader)), width(), st::emojiPanHeader), st::emojiPanHeaderBg->b); + } + p.setFont(st::emojiPanHeaderFont->f); + p.setPen(st::emojiPanHeaderColor->p); + p.drawTextLeft(st::emojiPanHeaderLeft, qMax(y - int(st::emojiPanHeader), _top) + st::emojiPanHeaderTop, width(), lang(LangKey(lng_emoji_category0 + emojiTabCount))); +} + +void EmojiPanInner::mousePressEvent(QMouseEvent *e) { + _lastMousePos = e->globalPos(); + updateSelected(); + if (!_picker.isHidden() && _selected == _pickerSel) { + _picker.hideStart(); + return; + } + _pressedSel = _selected; + + if (_selected >= 0) { + int tab = (_selected / emojiTabShift), sel = _selected % emojiTabShift; + if (tab < emojiTabCount && sel < _emojis[tab].size() && _emojis[tab][sel]->color) { + _pickerSel = _selected; + if (cEmojiVariants().constFind(_emojis[tab][sel]->code) == cEmojiVariants().cend()) { + onShowPicker(); + } else { + _showPickerTimer.start(500); + } + } + } +} + +void EmojiPanInner::mouseReleaseEvent(QMouseEvent *e) { + int32 pressed = _pressedSel; + _pressedSel = -1; + + _lastMousePos = e->globalPos(); + if (!_picker.isHidden()) { + if (_picker.rect().contains(_picker.mapFromGlobal(_lastMousePos))) { + return _picker.mouseReleaseEvent(0); + } else if (_pickerSel >= 0) { + int tab = (_pickerSel / emojiTabShift), sel = _pickerSel % emojiTabShift; + if (tab < emojiTabCount && sel < _emojis[tab].size() && _emojis[tab][sel]->color) { + if (cEmojiVariants().constFind(_emojis[tab][sel]->code) != cEmojiVariants().cend()) { + _picker.hideStart(); + } + } + } + } + updateSelected(); + + if (_showPickerTimer.isActive()) { + _showPickerTimer.stop(); + _pickerSel = -1; + _picker.hide(); + } + + if (_selected < 0 || _selected != pressed) return; + if (_selected >= emojiTabCount * emojiTabShift) { + int sel = _selected - (emojiTabCount * emojiTabShift); + if (sel >= _stickers.size()) { + RecentStickerPack recent(cRecentStickers()); + DocumentData *sticker = _stickers.at(sel - _stickers.size()); + for (int32 i = 0, l = recent.size(); i < l; ++i) { + if (recent.at(i).first == sticker) { + recent.removeAt(i); + cSetRecentStickers(recent); + Local::writeRecentStickers(); + refreshStickers(); + updateSelected(); + break; + } + } + } else { + emit stickerSelected(_stickers[sel]); + } + return; + } + int tab = (_selected / emojiTabShift), sel = _selected % emojiTabShift; + if (sel < _emojis[tab].size()) { + EmojiPtr emoji(_emojis[tab][sel]); + if (emoji->color && !_picker.isHidden()) return; + + selectEmoji(emoji); + } +} + +void EmojiPanInner::selectEmoji(EmojiPtr emoji) { + RecentEmojiPack &recent(cGetRecentEmojis()); + RecentEmojiPack::iterator i = recent.begin(), e = recent.end(); + for (; i != e; ++i) { + if (i->first == emoji) { + ++i->second; + if (i->second > 0x8000) { + for (RecentEmojiPack::iterator j = recent.begin(); j != e; ++j) { + if (j->second > 1) { + j->second /= 2; + } else { + j->second = 1; + } + } + } + for (; i != recent.begin(); --i) { + if ((i - 1)->second > i->second) { + break; + } + qSwap(*i, *(i - 1)); + } + break; + } + } + if (i == e) { + while (recent.size() >= EmojiPadPerRow * EmojiPadRowsPerPage) recent.pop_back(); + recent.push_back(qMakePair(emoji, 1)); + for (i = recent.end() - 1; i != recent.begin(); --i) { + if ((i - 1)->second > i->second) { + break; + } + qSwap(*i, *(i - 1)); + } + } + _saveConfigTimer.start(SaveRecentEmojisTimeout); + + emit emojiSelected(emoji); +} + +void EmojiPanInner::onSaveConfig() { + Local::writeUserSettings(); +} + +void EmojiPanInner::onShowPicker() { + int tab = (_pickerSel / emojiTabShift), sel = _pickerSel % emojiTabShift; + if (tab < emojiTabCount && sel < _emojis[tab].size() && _emojis[tab][sel]->color) { + int32 y = 0; + for (int c = 0; c <= tab; ++c) { + int32 size = (c == tab) ? (sel - (sel % EmojiPadPerRow)) : _counts[c], rows = (size / EmojiPadPerRow) + ((size % EmojiPadPerRow) ? 1 : 0); + y += st::emojiPanHeader + (rows * st::emojiPanSize.height()); + } + y -= _picker.height() - st::emojiPanRound; + if (y < _top) { + y += _picker.height() - st::emojiPanRound + st::emojiPanSize.height() - st::emojiPanRound; + } + int xmax = width() - _picker.width() - st::emojiPanPadding.right(); + _picker.move(qRound(xmax * float64(sel % EmojiPadPerRow) / float64(EmojiPadPerRow - 1)), y); + + _picker.showEmoji(_emojis[tab][sel]->code); + emit disableScroll(true); + } +} + +void EmojiPanInner::onPickerHidden() { + _pickerSel = -1; + update(); + emit disableScroll(false); + + _lastMousePos = QCursor::pos(); + updateSelected(); +} + +void EmojiPanInner::onColorSelected(EmojiPtr emoji) { + if (emoji->color) { + cRefEmojiVariants().insert(emoji->code, emojiKey(emoji)); + } + if (_pickerSel >= 0) { + int tab = (_pickerSel / emojiTabShift), sel = _pickerSel % emojiTabShift; + if (tab > 0 && tab < emojiTabCount) { + _emojis[tab][sel] = emoji; + update(); + } + } + selectEmoji(emoji); + _picker.hideStart(); +} + +void EmojiPanInner::mouseMoveEvent(QMouseEvent *e) { + _lastMousePos = e->globalPos(); + if (!_picker.isHidden()) { + if (_picker.rect().contains(_picker.mapFromGlobal(_lastMousePos))) { + return _picker.mouseMoveEvent(0); + } else { + _picker.clearSelection(); + } + } + updateSelected(); +} + +void EmojiPanInner::leaveEvent(QEvent *e) { + clearSelection(); +} + +void EmojiPanInner::leaveToChildEvent(QEvent *e) { + clearSelection(); +} + +void EmojiPanInner::enterFromChildEvent(QEvent *e) { + _lastMousePos = QCursor::pos(); + updateSelected(); +} + +void EmojiPanInner::clearSelection(bool fast) { + _lastMousePos = mapToGlobal(QPoint(-10, -10)); + if (fast) { + for (EmojiAnimations::const_iterator i = _emojiAnimations.cbegin(); i != _emojiAnimations.cend(); ++i) { + int index = qAbs(i.key()) - 1, tab = (index / emojiTabShift), sel = index % emojiTabShift; + _hovers[tab][sel] = 0; + } + _emojiAnimations.clear(); + _selected = _pressedSel = -1; + anim::stop(this); + } else { + updateSelected(); + } +} + +DBIEmojiTab EmojiPanInner::currentTab(int yOffset) const { + int y, ytill = 0; + for (int c = 0; c < emojiTabCount; ++c) { + int cnt = _counts[c]; + y = ytill; + ytill = y + st::emojiPanHeader + ((cnt / EmojiPadPerRow) + ((cnt % EmojiPadPerRow) ? 1 : 0)) * st::emojiPanSize.height(); + if (yOffset < ytill) { + return emojiTabAtIndex(c); + } + } + return dbietStickers; +} + +void EmojiPanInner::refreshStickers() { + clearSelection(true); + int32 cnt = cRecentStickers().size(); + _hovers[emojiTabCount] = QVector(cnt * 2, 0); + _stickers.resize(cnt); + _isUserGen.resize(cnt); + for (int32 i = 0; i < cnt; ++i) { + _stickers[i] = cRecentStickers().at(i).first; + _isUserGen[i] = (cRecentStickers().at(i).second < 0); + } +} + +void EmojiPanInner::hideFinish() { + if (!_picker.isHidden()) { + _picker.hideStart(true); + _pickerSel = -1; + } +} + +void EmojiPanInner::refreshRecent() { + clearSelection(true); + _count -= _counts[0]; + _counts[0] = emojiPackCount(dbietRecent); + _count += _counts[0]; + if (_hovers[0].size() != _counts[0]) _hovers[0] = QVector(_counts[0], 0); + _emojis[0] = emojiPack(dbietRecent); +} + +void EmojiPanInner::updateSelected() { + if (_pressedSel >= 0 || _pickerSel >= 0) return; + + int32 selIndex = -1; + QPoint p(mapFromGlobal(_lastMousePos)); + + int y, ytill = 0; + for (int c = 0; c < emojiTabCount; ++c) { + int cnt = _counts[c]; + y = ytill; + ytill = y + st::emojiPanHeader + ((cnt / EmojiPadPerRow) + ((cnt % EmojiPadPerRow) ? 1 : 0)) * st::emojiPanSize.height(); + if (p.y() >= y && p.y() < ytill) { + y += st::emojiPanHeader; + if (p.y() >= y && p.x() >= 0 && p.x() < EmojiPadPerRow * st::emojiPanSize.width()) { + selIndex = qFloor((p.y() - y) / st::emojiPanSize.height()) * EmojiPadPerRow + qFloor(p.x() / st::emojiPanSize.width()); + if (selIndex >= _emojis[c].size()) { + selIndex = -1; + } else { + selIndex += c * emojiTabShift; + } + } + break; + } + } + ytill += st::emojiPanHeader; + if (p.y() >= ytill) { + float64 stickerWidth = width() / float64(StickerPadPerRow); + int32 stickerSize = int32(stickerWidth); + if (p.x() >= 0 && p.x() < StickerPadPerRow * stickerWidth) { + selIndex = qFloor((p.y() - ytill) / stickerSize) * StickerPadPerRow + qFloor(p.x() / stickerWidth); + if (selIndex >= _stickers.size()) { + selIndex = -1; + } else { + int32 inx = p.x() - (selIndex % StickerPadPerRow) * stickerWidth, iny = p.y() - ytill - ((selIndex / StickerPadPerRow) * stickerSize); + if (inx >= stickerWidth - st::stickerPanDelete.pxWidth() && iny < st::stickerPanDelete.pxHeight()) { + selIndex = _stickers.size() + selIndex; + } + selIndex += emojiTabCount * emojiTabShift; + } + } + + } + + bool startanim = false; + int oldSel = _selected, xOldSel = -1, newSel = selIndex, xNewSel = -1; + if (oldSel >= emojiTabCount * emojiTabShift + _stickers.size()) { + xOldSel = oldSel; + oldSel -= _stickers.size(); + } + if (newSel >= emojiTabCount * emojiTabShift + _stickers.size()) { + xNewSel = newSel; + newSel -= _stickers.size(); + } + if (newSel != oldSel) { + if (oldSel >= 0) { + _emojiAnimations.remove(oldSel + 1); + if (_emojiAnimations.find(-oldSel - 1) == _emojiAnimations.end()) { + if (_emojiAnimations.isEmpty()) startanim = true; + _emojiAnimations.insert(-oldSel - 1, getms()); + } + } + if (newSel >= 0) { + _emojiAnimations.remove(-newSel - 1); + if (_emojiAnimations.find(newSel + 1) == _emojiAnimations.end()) { + if (_emojiAnimations.isEmpty()) startanim = true; + _emojiAnimations.insert(newSel + 1, getms()); + } + } + setCursor((newSel >= 0) ? style::cur_pointer : style::cur_default); + if (newSel >= 0 && !_picker.isHidden()) { + if (newSel != _pickerSel) { + _picker.hideStart(); + } else { + _picker.showStart(); + } + } + } + if (xNewSel != xOldSel) { + if (xOldSel >= 0) { + _emojiAnimations.remove(xOldSel + 1); + if (_emojiAnimations.find(-xOldSel - 1) == _emojiAnimations.end()) { + if (_emojiAnimations.isEmpty()) startanim = true; + _emojiAnimations.insert(-xOldSel - 1, getms()); + } + } + if (xNewSel >= 0) { + _emojiAnimations.remove(-xNewSel - 1); + if (_emojiAnimations.find(xNewSel + 1) == _emojiAnimations.end()) { + if (_emojiAnimations.isEmpty()) startanim = true; + _emojiAnimations.insert(xNewSel + 1, getms()); + } + } + } + _selected = selIndex; if (startanim) anim::start(this); } bool EmojiPanInner::animStep(float64 ms) { uint64 now = getms(); for (EmojiAnimations::iterator i = _emojiAnimations.begin(); i != _emojiAnimations.end();) { + int index = qAbs(i.key()) - 1, tab = (index / emojiTabShift), sel = index % emojiTabShift; float64 dt = float64(now - i.value()) / st::emojiPanDuration; if (dt >= 1) { - _hovers[qAbs(i.key()) - 1] = (i.key() > 0) ? 1 : 0; + _hovers[tab][sel] = (i.key() > 0) ? 1 : 0; i = _emojiAnimations.erase(i); } else { - _hovers[qAbs(i.key()) - 1] = (i.key() > 0) ? dt : (1 - dt); + _hovers[tab][sel] = (i.key() > 0) ? dt : (1 - dt); ++i; } } @@ -717,96 +1276,80 @@ bool EmojiPanInner::animStep(float64 ms) { } void EmojiPanInner::showEmojiPack(DBIEmojiTab packIndex) { - _tab = packIndex; - int32 h, size; - if (packIndex == dbietStickers) { - _emojis.clear(); + clearSelection(true); - float64 stickerWidth = width() / float64(StickerPadPerRow); - int32 stickerSize = int32(stickerWidth); + int32 h = countHeight(); + if (h != height()) resize(width(), h); - int32 l = cRecentStickers().size(); - _stickers.resize(l); - _isUserGen.resize(l); - for (int32 i = 0; i < l; ++i) { - DocumentData *sticker = _stickers[i] = cRecentStickers().at(i).first; - _isUserGen[i] = (cRecentStickers().at(i).second < 0); - if (i < StickerPadPerRow * ((EmojiPadRowsPerPage * st::emojiPanSize.height() - int(st::emojiPanSub)) / stickerSize + 1)) { - bool already = !sticker->already().isEmpty(), hasdata = !sticker->data.isEmpty(); - if (!sticker->loader && sticker->status != FileFailed && !already && !hasdata) { - sticker->save(QString()); - } - } - } - - size = _stickers.size(); - h = ((size / StickerPadPerRow) + ((size % StickerPadPerRow) ? 1 : 0)) * stickerSize; - _hovers = QVector(size * 2, 0); - } else { - _emojis = emojiPack(packIndex); - _stickers.clear(); - _isUserGen.clear(); - - size = _emojis.size(); - h = ((size / EmojiPadPerRow) + ((size % EmojiPadPerRow) ? 1 : 0)) * st::emojiPanSize.height(); - _hovers = QVector(size, 0); + int32 y = 0; + for (int c = 0; c < emojiTabCount; ++c) { + if (emojiTabAtIndex(c) == packIndex) break; + int rows = (_counts[c] / EmojiPadPerRow) + ((_counts[c] % EmojiPadPerRow) ? 1 : 0); + y += st::emojiPanHeader + rows * st::emojiPanSize.height(); } - h = qMax(h, EmojiPadRowsPerPage * st::emojiPanSize.height() - int(st::emojiPanSub)); - _emojiAnimations.clear(); - _selected = _pressedSel = -1; - resize(width(), h); + + emit scrollToY(y); + _lastMousePos = QCursor::pos(); - updateSelected(); + update(); } EmojiPan::EmojiPan(QWidget *parent) : TWidget(parent), -_hiding(false), a_opacity(0), _shadow(st::dropdownDef.shadow), -_recent (this, qsl("emoji_group"), dbietRecent , QString(), cEmojiTab() == dbietRecent , st::rbEmojiRecent), -_people (this, qsl("emoji_group"), dbietPeople , QString(), cEmojiTab() == dbietPeople , st::rbEmojiPeople), -_nature (this, qsl("emoji_group"), dbietNature , QString(), cEmojiTab() == dbietNature , st::rbEmojiNature), -_objects (this, qsl("emoji_group"), dbietObjects , QString(), cEmojiTab() == dbietObjects , st::rbEmojiObjects), -_places (this, qsl("emoji_group"), dbietPlaces , QString(), cEmojiTab() == dbietPlaces , st::rbEmojiPlaces), -_symbols (this, qsl("emoji_group"), dbietSymbols , QString(), cEmojiTab() == dbietSymbols , st::rbEmojiSymbols), -_stickers(this, qsl("emoji_group"), dbietStickers, QString(), cEmojiTab() == dbietStickers, st::rbEmojiStickers), +_noTabUpdate(false), _hiding(false), a_opacity(0), _shadow(st::dropdownDef.shadow), +_recent(this , qsl("emoji_group"), dbietRecent , QString(), true , st::rbEmojiRecent), +_people(this , qsl("emoji_group"), dbietPeople , QString(), false, st::rbEmojiPeople), +_nature(this , qsl("emoji_group"), dbietNature , QString(), false, st::rbEmojiNature), +_food(this , qsl("emoji_group"), dbietFood , QString(), false, st::rbEmojiFood), +_celebration(this, qsl("emoji_group"), dbietCelebration, QString(), false, st::rbEmojiCelebration), +_activity(this , qsl("emoji_group"), dbietActivity , QString(), false, st::rbEmojiActivity), +_travel(this , qsl("emoji_group"), dbietTravel , QString(), false, st::rbEmojiTravel), +_objects(this , qsl("emoji_group"), dbietObjects , QString(), false, st::rbEmojiObjects), +_stickers(this , qsl("emoji_group"), dbietStickers , QString(), false, st::rbEmojiStickers), _scroll(this, st::emojiScroll), _inner() { setFocusPolicy(Qt::NoFocus); _scroll.setFocusPolicy(Qt::NoFocus); _scroll.viewport()->setFocusPolicy(Qt::NoFocus); - if (cEmojiTab() != dbietStickers) { - _inner.showEmojiPack(cEmojiTab()); - } - - _scroll.setGeometry(st::dropdownDef.padding.left() + st::emojiPanPadding.left(), st::dropdownDef.padding.top() + _recent.height() + st::emojiPanPadding.top(), st::emojiPanPadding.left() + _inner.width() + st::emojiPanPadding.right(), EmojiPadRowsPerPage * st::emojiPanSize.height() - st::emojiPanSub); + _scroll.setGeometry(st::dropdownDef.padding.left() + st::emojiPanPadding.left(), st::dropdownDef.padding.top() + _recent.height() + st::emojiPanPadding.top(), st::emojiPanPadding.left() + _inner.width() + st::emojiPanPadding.right(), EmojiPadRowsPerPage * st::emojiPanSize.height() + st::emojiPanHeader); _scroll.setWidget(&_inner); + _inner.setAttribute(Qt::WA_OpaquePaintEvent); + _scroll.setAutoFillBackground(true); + _width = st::dropdownDef.padding.left() + st::emojiPanPadding.left() + _scroll.width() + st::emojiPanPadding.right() + st::dropdownDef.padding.right(); _height = st::dropdownDef.padding.top() + _recent.height() + st::emojiPanPadding.top() + _scroll.height() + st::emojiPanPadding.bottom() + st::dropdownDef.padding.bottom(); resize(_width, _height); - int32 left = st::dropdownDef.padding.left() + (_width - st::dropdownDef.padding.left() - st::dropdownDef.padding.right() - 7 * _recent.width()) / 2; + int32 left = st::dropdownDef.padding.left() + (_width - st::dropdownDef.padding.left() - st::dropdownDef.padding.right() - 9 * _recent.width()) / 2; int32 top = st::dropdownDef.padding.top(); - _recent.move(left, top); left += _recent.width(); - _people.move(left, top); left += _people.width(); - _nature.move(left, top); left += _nature.width(); - _objects.move(left, top); left += _objects.width(); - _places.move(left, top); left += _places.width(); - _symbols.move(left, top); left += _symbols.width(); - _stickers.move(left, top); left += _stickers.width(); + _recent.move(left , top); left += _recent.width(); + _people.move(left , top); left += _people.width(); + _nature.move(left , top); left += _nature.width(); + _food.move(left , top); left += _food.width(); + _celebration.move(left, top); left += _celebration.width(); + _activity.move(left , top); left += _activity.width(); + _travel.move(left , top); left += _travel.width(); + _objects.move(left , top); left += _objects.width(); + _stickers.move(left , top); left += _stickers.width(); _hideTimer.setSingleShot(true); connect(&_hideTimer, SIGNAL(timeout()), this, SLOT(hideStart())); - connect(&_recent , SIGNAL(changed()), this, SLOT(onTabChange())); - connect(&_people , SIGNAL(changed()), this, SLOT(onTabChange())); - connect(&_nature , SIGNAL(changed()), this, SLOT(onTabChange())); - connect(&_objects , SIGNAL(changed()), this, SLOT(onTabChange())); - connect(&_places , SIGNAL(changed()), this, SLOT(onTabChange())); - connect(&_symbols , SIGNAL(changed()), this, SLOT(onTabChange())); - connect(&_stickers, SIGNAL(changed()), this, SLOT(onTabChange())); + connect(&_inner, SIGNAL(scrollToY(int)), &_scroll, SLOT(scrollToY(int))); + connect(&_inner, SIGNAL(disableScroll(bool)), &_scroll, SLOT(disableScroll(bool))); - connect(&_scroll, SIGNAL(scrolled()), &_inner, SLOT(updateSelected())); + connect(&_recent , SIGNAL(changed()), this, SLOT(onTabChange())); + connect(&_people , SIGNAL(changed()), this, SLOT(onTabChange())); + connect(&_nature , SIGNAL(changed()), this, SLOT(onTabChange())); + connect(&_food , SIGNAL(changed()), this, SLOT(onTabChange())); + connect(&_celebration, SIGNAL(changed()), this, SLOT(onTabChange())); + connect(&_activity , SIGNAL(changed()), this, SLOT(onTabChange())); + connect(&_travel , SIGNAL(changed()), this, SLOT(onTabChange())); + connect(&_objects , SIGNAL(changed()), this, SLOT(onTabChange())); + connect(&_stickers , SIGNAL(changed()), this, SLOT(onTabChange())); + + connect(&_scroll, SIGNAL(scrolled()), this, SLOT(onScroll())); connect(&_inner, SIGNAL(emojiSelected(EmojiPtr)), this, SIGNAL(emojiSelected(EmojiPtr))); connect(&_inner, SIGNAL(stickerSelected(DocumentData*)), this, SIGNAL(stickerSelected(DocumentData*))); @@ -831,11 +1374,13 @@ void EmojiPan::paintEvent(QPaintEvent *e) { QRect r(st::dropdownDef.padding.left(), st::dropdownDef.padding.top(), _width - st::dropdownDef.padding.left() - st::dropdownDef.padding.right(), _height - st::dropdownDef.padding.top() - st::dropdownDef.padding.bottom()); - // draw shadow _shadow.paint(p, r); if (_cache.isNull()) { - p.fillRect(r, st::white->b); + p.fillRect(r.left(), r.top(), r.width(), _scroll.y() - r.top(), st::white->b); + p.fillRect(r.left(), _scroll.y(), _scroll.x() - r.left(), _scroll.height(), st::white->b); + p.fillRect(_scroll.x() + _scroll.width(), _scroll.y(), r.left() + r.width() - _scroll.x() - _scroll.width(), _scroll.height(), st::white->b); + p.fillRect(r.left(), _scroll.y() + _scroll.height(), r.width(), r.top() + r.height() - _scroll.y() - _scroll.height(), st::white->b); } else { p.drawPixmap(r.left(), r.top(), _cache); } @@ -877,6 +1422,10 @@ void EmojiPan::fastHide() { _cache = QPixmap(); } +void EmojiPan::refreshStickers() { + _inner.refreshStickers(); +} + bool EmojiPan::animStep(float64 ms) { float64 dt = ms / st::dropdownDef.duration; bool res = true; @@ -909,6 +1458,7 @@ void EmojiPan::hideStart() { void EmojiPan::hideFinish() { hide(); + _inner.hideFinish(); _cache = QPixmap(); _recent.setChecked(true); } @@ -917,6 +1467,9 @@ void EmojiPan::showStart() { if (!isHidden() && a_opacity.current() == 1) { return; } + if (isHidden()) { + _inner.refreshRecent(); + } if (_cache.isNull()) { showAll(); _cache = myGrab(this, rect().marginsRemoved(st::dropdownDef.padding)); @@ -926,7 +1479,7 @@ void EmojiPan::showStart() { show(); a_opacity.start(1); anim::start(this); - if (_stickers.checked()) emit updateStickers(); + emit updateStickers(); } bool EmojiPan::eventFilter(QObject *obj, QEvent *e) { @@ -956,9 +1509,11 @@ void EmojiPan::showAll() { _recent.show(); _people.show(); _nature.show(); + _food.show(); + _celebration.show(); + _activity.show(); + _travel.show(); _objects.show(); - _places.show(); - _symbols.show(); _stickers.show(); _scroll.show(); } @@ -967,31 +1522,51 @@ void EmojiPan::hideAll() { _recent.hide(); _people.hide(); _nature.hide(); + _food.hide(); + _celebration.hide(); + _activity.hide(); + _travel.hide(); _objects.hide(); - _places.hide(); - _symbols.hide(); _stickers.hide(); _scroll.hide(); _inner.clearSelection(true); } void EmojiPan::onTabChange() { + if (_noTabUpdate) return; DBIEmojiTab newTab = dbietRecent; if (_people.checked()) newTab = dbietPeople; else if (_nature.checked()) newTab = dbietNature; + else if (_food.checked()) newTab = dbietFood; + else if (_celebration.checked()) newTab = dbietCelebration; + else if (_activity.checked()) newTab = dbietActivity; + else if (_travel.checked()) newTab = dbietTravel; else if (_objects.checked()) newTab = dbietObjects; - else if (_places.checked()) newTab = dbietPlaces; - else if (_symbols.checked()) newTab = dbietSymbols; else if (_stickers.checked()) newTab = dbietStickers; - if (newTab != cEmojiTab()) { - cSetEmojiTab(newTab); - Local::writeUserSettings(); - _scroll.scrollToY(0); - } _inner.showEmojiPack(newTab); - if (newTab == dbietStickers) { - emit updateStickers(); +} + +void EmojiPan::onScroll() { + int top = _scroll.scrollTop(); + DBIEmojiTab tab = _inner.currentTab(top); + FlatRadiobutton *check = 0; + switch (tab) { + case dbietRecent : check = &_recent ; break; + case dbietPeople : check = &_people ; break; + case dbietNature : check = &_nature ; break; + case dbietFood : check = &_food ; break; + case dbietCelebration: check = &_celebration; break; + case dbietActivity : check = &_activity ; break; + case dbietTravel : check = &_travel ; break; + case dbietObjects : check = &_objects ; break; + case dbietStickers : check = &_stickers ; break; } + if (check && !check->checked()) { + _noTabUpdate = true; + check->setChecked(true); + _noTabUpdate = false; + } + _inner.setScrollTop(top); } MentionsInner::MentionsInner(MentionsDropdown *parent, MentionRows *rows, HashtagRows *hrows) : _parent(parent), _rows(rows), _hrows(hrows), _sel(-1), _mouseSel(false), _overDelete(false) { diff --git a/Telegram/SourceFiles/dropdown.h b/Telegram/SourceFiles/dropdown.h index e3b41b4c6..3b1f78371 100644 --- a/Telegram/SourceFiles/dropdown.h +++ b/Telegram/SourceFiles/dropdown.h @@ -132,7 +132,68 @@ private: }; -class EmojiPanInner : public QWidget, public Animated { +static const int EmojiColorsCount = 5; + +class EmojiColorPicker : public TWidget, public Animated { + Q_OBJECT + +public: + + EmojiColorPicker(QWidget *parent); + + void showEmoji(uint32 code); + + void paintEvent(QPaintEvent *e); + void enterEvent(QEvent *e); + void leaveEvent(QEvent *e); + void mousePressEvent(QMouseEvent *e); + void mouseReleaseEvent(QMouseEvent *e); + void mouseMoveEvent(QMouseEvent *e); + + bool animStep(float64 ms); + void showStart(); + + void clearSelection(bool fast = false); + +public slots: + + void hideStart(bool fast = false); + +signals: + + void emojiSelected(EmojiPtr emoji); + void hidden(); + +private: + + void drawVariant(Painter &p, int variant); + + void updateSelected(); + + bool _ignoreShow; + + EmojiPtr _variants[EmojiColorsCount + 1]; + + typedef QMap EmojiAnimations; // index - showing, -index - hiding + EmojiAnimations _emojiAnimations; + + float64 _hovers[EmojiColorsCount + 1]; + + int32 _selected, _pressedSel; + QPoint _lastMousePos; + + bool _hiding; + QPixmap _cache; + + anim::fvalue a_opacity; + + QTimer _hideTimer; + + BoxShadow _shadow; + +}; + +class EmojiPanInner : public TWidget, public Animated { Q_OBJECT public: @@ -145,39 +206,67 @@ public: void mouseReleaseEvent(QMouseEvent *e); void mouseMoveEvent(QMouseEvent *e); void leaveEvent(QEvent *e); + void leaveToChildEvent(QEvent *e); + void enterFromChildEvent(QEvent *e); bool animStep(float64 ms); + void hideFinish(); void showEmojiPack(DBIEmojiTab packIndex); void clearSelection(bool fast = false); + + DBIEmojiTab currentTab(int yOffset) const; + + void refreshStickers(); + void refreshRecent(); + + void setScrollTop(int top); public slots: void updateSelected(); void onSaveConfig(); + void onShowPicker(); + void onPickerHidden(); + void onColorSelected(EmojiPtr emoji); + signals: void emojiSelected(EmojiPtr emoji); void stickerSelected(DocumentData *sticker); + void scrollToY(int y); + void disableScroll(bool dis); + private: + int countHeight(); + void selectEmoji(EmojiPtr emoji); + typedef QMap EmojiAnimations; // index - showing, -index - hiding EmojiAnimations _emojiAnimations; + int _top; + int _counts[emojiTabCount], _count; + StickerPack _stickers; QVector _isUserGen; - QVector _emojis; - QVector _hovers; + QVector _emojis[emojiTabCount]; + QVector _hovers[emojiTabCount + 1]; // + stickers hovers and stickers-x hovers - DBIEmojiTab _tab; - int32 _selected, _xSelected, _pressedSel, _xPressedSel; + float64 _stickerWidth; + int32 _esize, _stickerSize; + + int32 _selected, _pressedSel, _pickerSel; QPoint _lastMousePos; QTimer _saveConfigTimer; + EmojiColorPicker _picker; + QTimer _showPickerTimer; + }; class EmojiPan : public TWidget, public Animated { @@ -203,6 +292,8 @@ public: bool eventFilter(QObject *obj, QEvent *e); + void refreshStickers(); + public slots: void hideStart(); @@ -212,6 +303,7 @@ public slots: void onWndActiveChanged(); void onTabChange(); + void onScroll(); signals: @@ -224,6 +316,8 @@ private: void showAll(); void hideAll(); + bool _noTabUpdate; + int32 _width, _height; bool _hiding; QPixmap _cache; @@ -234,7 +328,7 @@ private: BoxShadow _shadow; - FlatRadiobutton _recent, _people, _nature, _objects, _places, _symbols, _stickers; + FlatRadiobutton _recent, _people, _nature, _food, _celebration, _activity, _travel, _objects, _stickers; int32 _emojiPack; ScrollArea _scroll; diff --git a/Telegram/SourceFiles/gui/emoji_config.cpp b/Telegram/SourceFiles/gui/emoji_config.cpp index e8d0701b5..9c5e84326 100644 --- a/Telegram/SourceFiles/gui/emoji_config.cpp +++ b/Telegram/SourceFiles/gui/emoji_config.cpp @@ -27,17 +27,19 @@ namespace { char emojisData[sizeof(EmojiData) * 1180]; } -int EmojiSizes[] = { 18, 22, 27, 36 }, ESize = 0; - -void initEmoji() { +int EmojiSizes[] = { 18, 22, 27, 36, 45 }, EIndex = -1, ESize = 0; +const char *EmojiNames[] = { ":/gui/art/emoji.webp", ":/gui/art/emoji_125x.webp", ":/gui/art/emoji_150x.webp", ":/gui/art/emoji_200x.webp", ":/gui/art/emoji_250x.webp" }, *EName = 0; +void emojiInit() { DBIScale emojiForScale = cRetina() ? dbisTwo : cScale(); switch (emojiForScale) { - case dbisOne: ESize = EmojiSizes[0]; break; - case dbisOneAndQuarter: ESize = EmojiSizes[1]; break; - case dbisOneAndHalf: ESize = EmojiSizes[2]; break; - case dbisTwo: ESize = EmojiSizes[3]; break; + case dbisOne: EIndex = 0; break; + case dbisOneAndQuarter: EIndex = 1; break; + case dbisOneAndHalf: EIndex = 2; break; + case dbisTwo: EIndex = 3; break; }; + ESize = EmojiSizes[EIndex]; + EName = EmojiNames[EIndex]; EmojiData *toFill = emojis = (EmojiData*)emojisData; @@ -1223,7 +1225,7 @@ void initEmoji() { new (toFill++) EmojiData(11, 16, 0xD83DDEC0U, 0, 4, 0, 0xD83CDFFFU); }; -EmojiPtr getEmoji(uint32 code) { +EmojiPtr emojiGet(uint32 code) { if (!emojis) return 0; uint32 highCode = code >> 16; @@ -1376,6 +1378,13 @@ EmojiPtr getEmoji(uint32 code) { return 0; } + if (highCode == 0xFFFFU) { + static const int sequenceOffset = 835; + + uint32 index = (code & 0xFFFFU); + return (index < 18) ? &emojis[sequenceOffset + index] : 0; + } + if (code < 0xD83CDC04U || code > 0xD83DDEC5U) return 0; switch (code) { @@ -1525,6 +1534,7 @@ EmojiPtr getEmoji(uint32 code) { case 0xD83CDF82U: return &emojis[271]; case 0xD83CDF83U: return &emojis[272]; case 0xD83CDF84U: return &emojis[273]; + case 0xD83CDF85U: return &emojis[274]; case 0xD83CDF86U: return &emojis[275]; case 0xD83CDF87U: return &emojis[276]; case 0xD83CDF88U: return &emojis[277]; @@ -1574,9 +1584,13 @@ EmojiPtr getEmoji(uint32 code) { case 0xD83CDFC0U: return &emojis[321]; case 0xD83CDFC1U: return &emojis[322]; case 0xD83CDFC2U: return &emojis[323]; + case 0xD83CDFC3U: return &emojis[324]; + case 0xD83CDFC4U: return &emojis[325]; case 0xD83CDFC6U: return &emojis[326]; + case 0xD83CDFC7U: return &emojis[327]; case 0xD83CDFC8U: return &emojis[328]; case 0xD83CDFC9U: return &emojis[329]; + case 0xD83CDFCAU: return &emojis[330]; case 0xD83CDFE0U: return &emojis[331]; case 0xD83CDFE1U: return &emojis[332]; case 0xD83CDFE2U: return &emojis[333]; @@ -1658,8 +1672,21 @@ EmojiPtr getEmoji(uint32 code) { case 0xD83DDC3DU: return &emojis[409]; case 0xD83DDC3EU: return &emojis[410]; case 0xD83DDC40U: return &emojis[411]; + case 0xD83DDC42U: return &emojis[412]; + case 0xD83DDC43U: return &emojis[413]; case 0xD83DDC44U: return &emojis[414]; case 0xD83DDC45U: return &emojis[415]; + case 0xD83DDC46U: return &emojis[416]; + case 0xD83DDC47U: return &emojis[417]; + case 0xD83DDC48U: return &emojis[418]; + case 0xD83DDC49U: return &emojis[419]; + case 0xD83DDC4AU: return &emojis[420]; + case 0xD83DDC4BU: return &emojis[421]; + case 0xD83DDC4CU: return &emojis[422]; + case 0xD83DDC4DU: return &emojis[423]; + case 0xD83DDC4EU: return &emojis[424]; + case 0xD83DDC4FU: return &emojis[425]; + case 0xD83DDC50U: return &emojis[426]; case 0xD83DDC51U: return &emojis[427]; case 0xD83DDC52U: return &emojis[428]; case 0xD83DDC53U: return &emojis[429]; @@ -1681,19 +1708,40 @@ EmojiPtr getEmoji(uint32 code) { case 0xD83DDC63U: return &emojis[445]; case 0xD83DDC64U: return &emojis[446]; case 0xD83DDC65U: return &emojis[447]; + case 0xD83DDC66U: return &emojis[448]; + case 0xD83DDC67U: return &emojis[449]; + case 0xD83DDC68U: return &emojis[450]; + case 0xD83DDC69U: return &emojis[451]; case 0xD83DDC6AU: return &emojis[452]; case 0xD83DDC6BU: return &emojis[453]; case 0xD83DDC6CU: return &emojis[454]; case 0xD83DDC6DU: return &emojis[455]; + case 0xD83DDC6EU: return &emojis[456]; case 0xD83DDC6FU: return &emojis[457]; + case 0xD83DDC70U: return &emojis[458]; + case 0xD83DDC71U: return &emojis[459]; + case 0xD83DDC72U: return &emojis[460]; + case 0xD83DDC73U: return &emojis[461]; + case 0xD83DDC74U: return &emojis[462]; + case 0xD83DDC75U: return &emojis[463]; + case 0xD83DDC76U: return &emojis[464]; + case 0xD83DDC77U: return &emojis[465]; + case 0xD83DDC78U: return &emojis[466]; case 0xD83DDC79U: return &emojis[467]; case 0xD83DDC7AU: return &emojis[468]; case 0xD83DDC7BU: return &emojis[469]; + case 0xD83DDC7CU: return &emojis[470]; case 0xD83DDC7DU: return &emojis[471]; case 0xD83DDC7EU: return &emojis[472]; case 0xD83DDC7FU: return &emojis[473]; case 0xD83DDC80U: return &emojis[474]; + case 0xD83DDC81U: return &emojis[475]; + case 0xD83DDC82U: return &emojis[476]; + case 0xD83DDC83U: return &emojis[477]; case 0xD83DDC84U: return &emojis[478]; + case 0xD83DDC85U: return &emojis[479]; + case 0xD83DDC86U: return &emojis[480]; + case 0xD83DDC87U: return &emojis[481]; case 0xD83DDC88U: return &emojis[482]; case 0xD83DDC89U: return &emojis[483]; case 0xD83DDC8AU: return &emojis[484]; @@ -1728,6 +1776,7 @@ EmojiPtr getEmoji(uint32 code) { case 0xD83DDCA7U: return &emojis[513]; case 0xD83DDCA8U: return &emojis[514]; case 0xD83DDCA9U: return &emojis[515]; + case 0xD83DDCAAU: return &emojis[516]; case 0xD83DDCABU: return &emojis[517]; case 0xD83DDCACU: return &emojis[518]; case 0xD83DDCADU: return &emojis[519]; @@ -1965,9 +2014,17 @@ EmojiPtr getEmoji(uint32 code) { case 0xD83DDE3EU: return &emojis[751]; case 0xD83DDE3FU: return &emojis[752]; case 0xD83DDE40U: return &emojis[753]; + case 0xD83DDE45U: return &emojis[754]; + case 0xD83DDE46U: return &emojis[755]; + case 0xD83DDE47U: return &emojis[756]; case 0xD83DDE48U: return &emojis[757]; case 0xD83DDE49U: return &emojis[758]; case 0xD83DDE4AU: return &emojis[759]; + case 0xD83DDE4BU: return &emojis[760]; + case 0xD83DDE4CU: return &emojis[761]; + case 0xD83DDE4DU: return &emojis[762]; + case 0xD83DDE4EU: return &emojis[763]; + case 0xD83DDE4FU: return &emojis[764]; case 0xD83DDE80U: return &emojis[765]; case 0xD83DDE81U: return &emojis[766]; case 0xD83DDE82U: return &emojis[767]; @@ -2003,6 +2060,7 @@ EmojiPtr getEmoji(uint32 code) { case 0xD83DDEA0U: return &emojis[797]; case 0xD83DDEA1U: return &emojis[798]; case 0xD83DDEA2U: return &emojis[799]; + case 0xD83DDEA3U: return &emojis[800]; case 0xD83DDEA4U: return &emojis[801]; case 0xD83DDEA5U: return &emojis[802]; case 0xD83DDEA6U: return &emojis[803]; @@ -2019,6 +2077,9 @@ EmojiPtr getEmoji(uint32 code) { case 0xD83DDEB1U: return &emojis[814]; case 0xD83DDEB2U: return &emojis[815]; case 0xD83DDEB3U: return &emojis[816]; + case 0xD83DDEB4U: return &emojis[817]; + case 0xD83DDEB5U: return &emojis[818]; + case 0xD83DDEB6U: return &emojis[819]; case 0xD83DDEB7U: return &emojis[820]; case 0xD83DDEB8U: return &emojis[821]; case 0xD83DDEB9U: return &emojis[822]; @@ -2028,6 +2089,7 @@ EmojiPtr getEmoji(uint32 code) { case 0xD83DDEBDU: return &emojis[826]; case 0xD83DDEBEU: return &emojis[827]; case 0xD83DDEBFU: return &emojis[828]; + case 0xD83DDEC0U: return &emojis[829]; case 0xD83DDEC1U: return &emojis[830]; case 0xD83DDEC2U: return &emojis[831]; case 0xD83DDEC3U: return &emojis[832]; @@ -2076,7 +2138,7 @@ EmojiPtr getEmoji(uint32 code) { return 0; } -EmojiPtr getEmoji(uint32 code, uint32 code2) { +EmojiPtr emojiGet(uint32 code, uint32 code2) { if (code < 0xD83CDDE6U || code > 0xD83CDDFFU) return 0; switch (code) { @@ -2187,7 +2249,7 @@ EmojiPtr getEmoji(uint32 code, uint32 code2) { return 0; } -EmojiPtr getEmoji(EmojiPtr emoji, uint32 color) { +EmojiPtr emojiGet(EmojiPtr emoji, uint32 color) { if (!emoji || ((emoji->color & 0xFFFF0000U) != 0xFFFF0000U)) return emoji; int index = 0; @@ -2203,7 +2265,9 @@ EmojiPtr getEmoji(EmojiPtr emoji, uint32 color) { return &emojis[(emoji->color & 0xFFFFU) + index]; } -EmojiPtr getEmoji(const QChar *from, const QChar *end) { +EmojiPtr emojiGet(const QChar *from, const QChar *end) { + static const int sequenceOffset = 835; + if (end < from + 8 || (from + 2)->unicode() != 0x200D || (from + 5)->unicode() != 0x200D) return 0; static const uint32 man = 0xD83DDC68, woman = 0xD83DDC69, boy = 0xD83DDC66, girl = 0xD83DDC67, heart = 0x2764FE0F, kiss = 0xD83DDC8B; @@ -2217,55 +2281,83 @@ EmojiPtr getEmoji(const QChar *from, const QChar *end) { if (one == man) { if (two == man) { if (three == girl) { - if (four == girl) return [13]; - if (four == boy) return [11]; + if (four == girl) return &emojis[sequenceOffset + 13]; + if (four == boy) return &emojis[sequenceOffset + 11]; } else if (three == boy) { - if (four == boy) return [12]; + if (four == boy) return &emojis[sequenceOffset + 12]; } } else if (two == woman) { if (three == girl) { - if (four == girl) return [3]; - if (four == boy) return [1]; + if (four == girl) return &emojis[sequenceOffset + 3]; + if (four == boy) return &emojis[sequenceOffset + 1]; } else if (three == boy) { - if (four == boy) return [2]; + if (four == boy) return &emojis[sequenceOffset + 2]; } } else if (two == heart) { - if (three == kiss && four == man) return [17]; + if (three == kiss && four == man) return &emojis[sequenceOffset + 17]; } } else { if (two == woman) { if (three == girl) { - if (four == girl) return [3]; - if (four == boy) return [1]; + if (four == girl) return &emojis[sequenceOffset + 8]; + if (four == boy) return &emojis[sequenceOffset + 6]; } else if (three == boy) { - if (four == boy) return [2]; + if (four == boy) return &emojis[sequenceOffset + 7]; } } else if (two == heart) { - if (three == kiss && four == woman) return [16]; + if (three == kiss && four == woman) return &emojis[sequenceOffset + 16]; } } } if (one == man) { if (two == man) { - if (three == girl) return [10]; - if (three == boy) return [9]; + if (three == girl) return &emojis[sequenceOffset + 10]; + if (three == boy) return &emojis[sequenceOffset + 9]; } else if (two == woman) { - if (three == girl) return [0]; + if (three == girl) return &emojis[sequenceOffset + 0]; } else if (two == heart) { - if (three == man) return [15]; + if (three == man) return &emojis[sequenceOffset + 15]; } } else { if (two == woman) { - if (three == girl) return [5]; - if (three == boy) return [4]; + if (three == girl) return &emojis[sequenceOffset + 5]; + if (three == boy) return &emojis[sequenceOffset + 4]; } else if (two == heart) { - if (three == woman) return [14]; + if (three == woman) return &emojis[sequenceOffset + 14]; } } return 0; } -void findEmoji(const QChar *ch, const QChar *e, const QChar *&newEmojiEnd, uint32 &emojiCode) { +QString emojiGetSequence(int index) { + static QVector sequences; + if (sequences.isEmpty()) { + sequences.reserve(18); + + sequences.push_back(QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7")); + sequences.push_back(QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa6")); + sequences.push_back(QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa6\xe2\x80\x8d\xf0\x9f\x91\xa6")); + sequences.push_back(QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa7")); + sequences.push_back(QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa6")); + sequences.push_back(QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7")); + sequences.push_back(QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa6")); + sequences.push_back(QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa6\xe2\x80\x8d\xf0\x9f\x91\xa6")); + sequences.push_back(QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa9\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa7")); + sequences.push_back(QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa6")); + sequences.push_back(QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa7")); + sequences.push_back(QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa6")); + sequences.push_back(QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa6\xe2\x80\x8d\xf0\x9f\x91\xa6")); + sequences.push_back(QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa8\xe2\x80\x8d\xf0\x9f\x91\xa7\xe2\x80\x8d\xf0\x9f\x91\xa7")); + sequences.push_back(QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xe2\x9d\xa4\xef\xb8\x8f\xe2\x80\x8d\xf0\x9f\x91\xa9")); + sequences.push_back(QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xe2\x9d\xa4\xef\xb8\x8f\xe2\x80\x8d\xf0\x9f\x91\xa8")); + sequences.push_back(QString::fromUtf8("\xf0\x9f\x91\xa9\xe2\x80\x8d\xe2\x9d\xa4\xef\xb8\x8f\xe2\x80\x8d\xf0\x9f\x92\x8b\xe2\x80\x8d\xf0\x9f\x91\xa9")); + sequences.push_back(QString::fromUtf8("\xf0\x9f\x91\xa8\xe2\x80\x8d\xe2\x9d\xa4\xef\xb8\x8f\xe2\x80\x8d\xf0\x9f\x92\x8b\xe2\x80\x8d\xf0\x9f\x91\xa8")); + } + + return (index >= 0 && index < sequences.size()) ? sequences.at(index) : QString(); +} + +void emojiFind(const QChar *ch, const QChar *e, const QChar *&newEmojiEnd, uint32 &emojiCode) { switch (ch->unicode()) { case '}': if (ch + 1 != e) switch ((ch + 1)->unicode()) { @@ -2703,6 +2795,20 @@ void findEmoji(const QChar *ch, const QChar *e, const QChar *&newEmojiEnd, uint3 } } +int emojiPackCount(DBIEmojiTab tab) { + switch (tab) { + case dbietRecent : return cGetRecentEmojis().size(); + case dbietPeople : return 153; + case dbietNature : return 125; + case dbietFood : return 58; + case dbietCelebration: return 39; + case dbietActivity : return 53; + case dbietTravel : return 122; + case dbietObjects : return 345; + }; + return 0; +} + EmojiPack emojiPack(DBIEmojiTab tab) { switch (tab) { @@ -3258,43 +3364,43 @@ EmojiPack emojiPack(DBIEmojiTab tab) { vTravel[77] = &emojis[69]; vTravel[78] = &emojis[341]; vTravel[79] = &emojis[342]; - vTravel[80] = &emojis[873]; - vTravel[81] = &emojis[873]; + vTravel[80] = &emojis[875]; + vTravel[81] = &emojis[874]; vTravel[82] = &emojis[876]; - vTravel[83] = &emojis[876]; + vTravel[83] = &emojis[877]; vTravel[84] = &emojis[878]; - vTravel[85] = &emojis[878]; - vTravel[86] = &emojis[878]; - vTravel[87] = &emojis[878]; - vTravel[88] = &emojis[883]; + vTravel[85] = &emojis[880]; + vTravel[86] = &emojis[881]; + vTravel[87] = &emojis[882]; + vTravel[88] = &emojis[884]; vTravel[89] = &emojis[886]; - vTravel[90] = &emojis[886]; + vTravel[90] = &emojis[887]; vTravel[91] = &emojis[883]; vTravel[92] = &emojis[889]; - vTravel[93] = &emojis[890]; + vTravel[93] = &emojis[893]; vTravel[94] = &emojis[890]; - vTravel[95] = &emojis[890]; - vTravel[96] = &emojis[890]; - vTravel[97] = &emojis[890]; + vTravel[95] = &emojis[891]; + vTravel[96] = &emojis[892]; + vTravel[97] = &emojis[894]; vTravel[98] = &emojis[895]; vTravel[99] = &emojis[896]; vTravel[100] = &emojis[897]; - vTravel[101] = &emojis[897]; - vTravel[102] = &emojis[897]; + vTravel[101] = &emojis[899]; + vTravel[102] = &emojis[898]; vTravel[103] = &emojis[900]; - vTravel[104] = &emojis[900]; - vTravel[105] = &emojis[900]; + vTravel[104] = &emojis[902]; + vTravel[105] = &emojis[901]; vTravel[106] = &emojis[903]; - vTravel[107] = &emojis[903]; - vTravel[108] = &emojis[903]; - vTravel[109] = &emojis[903]; + vTravel[107] = &emojis[904]; + vTravel[108] = &emojis[906]; + vTravel[109] = &emojis[905]; vTravel[110] = &emojis[907]; vTravel[111] = &emojis[908]; - vTravel[112] = &emojis[908]; + vTravel[112] = &emojis[910]; vTravel[113] = &emojis[914]; vTravel[114] = &emojis[885]; - vTravel[115] = &emojis[908]; - vTravel[116] = &emojis[878]; + vTravel[115] = &emojis[909]; + vTravel[116] = &emojis[879]; vTravel[117] = &emojis[911]; vTravel[118] = &emojis[888]; vTravel[119] = &emojis[912]; @@ -3664,5 +3770,6 @@ EmojiPack emojiPack(DBIEmojiTab tab) { for (RecentEmojiPack::const_iterator i = cGetRecentEmojis().cbegin(), e = cGetRecentEmojis().cend(); i != e; ++i) { result.push_back(i->first); } - return result;} + return result; +} diff --git a/Telegram/SourceFiles/gui/emoji_config.h b/Telegram/SourceFiles/gui/emoji_config.h index 0b6b6b998..958c89886 100644 --- a/Telegram/SourceFiles/gui/emoji_config.h +++ b/Telegram/SourceFiles/gui/emoji_config.h @@ -19,12 +19,94 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org #include "gui/text.h" -void initEmoji(); -EmojiPtr getEmoji(uint32 code); +void emojiInit(); +EmojiPtr emojiGet(uint32 code); +EmojiPtr emojiGet(uint32 code, uint32 code2); +EmojiPtr emojiGet(EmojiPtr emoji, uint32 color); +EmojiPtr emojiGet(const QChar *from, const QChar *end); +QString emojiGetSequence(int index); -extern int EmojiSizes[5], ESize; +inline uint64 emojiKey(EmojiPtr emoji) { + uint64 key = emoji->code; + if (emoji->code2) { + key = (key << 32) | uint64(emoji->code2); + } else if (emoji->color && ((emoji->color & 0xFFFF0000U) != 0xFFFF0000U)) { + key = (key << 32) | uint64(emoji->color); + } + return key; +} -void findEmoji(const QChar *ch, const QChar *e, const QChar *&newEmojiEnd, uint32 &emojiCode); +inline EmojiPtr emojiFromKey(uint64 key) { + uint32 code = uint32(key >> 32), code2 = uint32(key & 0xFFFFFFFFLLU); + if (!code && code2) { + code = code2; + code2 = 0; + } + EmojiPtr emoji = emojiGet(code); + if (emoji == TwoSymbolEmoji) { + return emojiGet(code, code2); + } else if (emoji && emoji->color && code2) { + return emojiGet(emoji, code2); + } + return emoji; +} + +inline EmojiPtr emojiFromUrl(const QString &url) { + return emojiFromKey(url.midRef(10).toULongLong(0, 16)); // skip emoji://e. +} + +inline EmojiPtr emojiFromText(const QChar *ch, const QChar *e, int &len) { + QString tmp(ch, e - ch); + QByteArray tmp2 = tmp.toUtf8(); + const char *tmp3 = tmp2.constData(); + EmojiPtr emoji = 0; + if (ch + 1 < e && ((ch->isHighSurrogate() && (ch + 1)->isLowSurrogate()) || (((ch->unicode() >= 48 && ch->unicode() < 58) || ch->unicode() == 35) && (ch + 1)->unicode() == 0x20E3))) { + uint32 code = (ch->unicode() << 16) | (ch + 1)->unicode(); + emoji = emojiGet(code); + if (emoji) { + if (emoji == TwoSymbolEmoji) { // check two symbol + if (ch + 3 >= e) { + emoji = 0; + } else { + uint32 code2 = ((uint32((ch + 2)->unicode()) << 16) | uint32((ch + 3)->unicode())); + emoji = emojiGet(code, code2); + } + } else { + if (ch + 2 < e && (ch + 2)->unicode() == 0x200D) { // check sequence + EmojiPtr seq = emojiGet(ch, e); + if (seq) { + emoji = seq; + } + } + } + } + } else if (ch < e) { + emoji = emojiGet(ch->unicode()); + Q_ASSERT(emoji != TwoSymbolEmoji); + } + + if (emoji) { + len = emoji->len + ((ch + emoji->len < e && (ch + emoji->len)->unicode() == 0xFE0F) ? 1 : 0); + if (emoji->color && (ch + len + 1 < e && (ch + len)->isHighSurrogate() && (ch + len + 1)->isLowSurrogate())) { // color + uint32 color = ((uint32((ch + len)->unicode()) << 16) | uint32((ch + len + 1)->unicode())); + EmojiPtr col = emojiGet(emoji, color); + if (col && col != emoji) { + len += col->len - emoji->len; + emoji = col; + if (ch + len < e && (ch + len)->unicode() == 0xFE0F) { + ++len; + } + } + } + } + + return emoji; +} + +extern int EmojiSizes[5], EIndex, ESize; +extern const char *EmojiNames[5], *EName; + +void emojiFind(const QChar *ch, const QChar *e, const QChar *&newEmojiEnd, uint32 &emojiCode); inline bool emojiEdge(const QChar *ch) { return true; @@ -50,7 +132,7 @@ inline QString replaceEmojis(const QString &text) { uint32 emojiCode = 0; const QChar *newEmojiEnd = 0; if (canFindEmoji) { - findEmoji(ch, e, newEmojiEnd, emojiCode); + emojiFind(ch, e, newEmojiEnd, emojiCode); } while (currentLink < lnkCount && ch >= lnkRanges[currentLink].from + lnkRanges[currentLink].len) { @@ -93,4 +175,5 @@ inline QString replaceEmojis(const QString &text) { return result; } +int emojiPackCount(DBIEmojiTab tab); EmojiPack emojiPack(DBIEmojiTab tab); diff --git a/Telegram/SourceFiles/gui/flattextarea.cpp b/Telegram/SourceFiles/gui/flattextarea.cpp index e7f4c6fbc..458780eca 100644 --- a/Telegram/SourceFiles/gui/flattextarea.cpp +++ b/Telegram/SourceFiles/gui/flattextarea.cpp @@ -174,8 +174,7 @@ EmojiPtr FlatTextarea::getSingleEmoji() const { if (!text.isEmpty()) { QTextCharFormat format = fragment.charFormat(); - QString imageName = static_cast(&format)->name(); - return getEmoji(imageName.mid(8).toUInt(0, 16)); + return emojiFromUrl(static_cast(&format)->name()); } return 0; } @@ -288,7 +287,7 @@ void FlatTextarea::getSingleEmojiFragment(QString &text, QTextFragment &fragment } if (f.isImageFormat() && !t.isEmpty() && t.at(0).unicode() == QChar::ObjectReplacementCharacter) { QString imageName = static_cast(&f)->name(); - if (imageName.midRef(0, 8) == qsl("emoji://")) { + if (imageName.startsWith(QLatin1String("emoji://e."))) { fragment = fr; text = t; return; @@ -372,10 +371,8 @@ QString FlatTextarea::getText(int32 start, int32 end) const { case QChar::ObjectReplacementCharacter: if (emojiText.isEmpty() && f.isImageFormat()) { QString imageName = static_cast(&f)->name(); - if (imageName.midRef(0, 8) == qsl("emoji://")) { - uint32 index = imageName.mid(8).toUInt(0, 16); - const EmojiData *emoji = getEmoji(index); - if (emoji) { + if (imageName.startsWith(QLatin1String("emoji://e."))) { + if (EmojiPtr emoji = emojiFromUrl(imageName)) { emojiText = textEmojiString(emoji); } } @@ -520,7 +517,7 @@ void FlatTextarea::insertEmoji(EmojiPtr emoji, QTextCursor c) { c.removeSelectedText(); QPixmap img(App::emojiSingle(emoji, _st.font->height)); - QString url = qsl("emoji://") + QString::number(emoji->code, 16); + QString url = qsl("emoji://e.") + QString::number(emojiKey(emoji), 16); document()->addResource(QTextDocument::ImageResource, QUrl(url), QVariant(img)); QTextImageFormat imageFormat; imageFormat.setWidth(img.width() / cIntRetinaFactor()); @@ -546,33 +543,20 @@ void FlatTextarea::processDocumentContentsChange(int position, int charsAdded) { QTextFragment fragment(iter.fragment()); if (!fragment.isValid()) continue; - int32 p = fragment.position(), e = p + fragment.length(); - if (p >= end || e <= start) { + int32 fp = fragment.position(), fe = fp + fragment.length(); + if (fp >= end || fe <= start) { continue; } QString t(fragment.text()); - for (const QChar *ch = t.constData(), *e = ch + t.size(); ch != e; ++ch) { - if (ch + 1 < e && (ch->isHighSurrogate() || (((ch->unicode() >= 48 && ch->unicode() < 58) || ch->unicode() == 35) && (ch + 1)->unicode() == 0x20E3))) { - emoji = getEmoji((ch->unicode() << 16) | (ch + 1)->unicode()); - if (emoji) { - if (emoji->len == 4 && (ch + 3 >= e || ((uint32((ch + 2)->unicode()) << 16) | uint32((ch + 3)->unicode())) != emoji->code2)) { - emoji = 0; - } else { - emojiPosition = p + (ch - t.constData()); - emojiLen = emoji->len + ((ch + emoji->len < e && (ch + emoji->len)->unicode() == 0xFE0F) ? 1 : 0); - break; - } - } - ++ch; - } else { - emoji = getEmoji(ch->unicode()); - if (emoji) { - emojiPosition = p + (ch - t.constData()); - emojiLen = emoji->len + ((ch + emoji->len < e && (ch + emoji->len)->unicode() == 0xFE0F) ? 1 : 0); - break; - } + const QChar *ch = t.constData(), *e = ch + t.size(); + for (; ch != e; ++ch) { + emoji = emojiFromText(ch, e, emojiLen); + if (emoji) { + emojiPosition = fp + (ch - t.constData()); + break; } + if (ch + 1 < e && ch->isHighSurrogate() && (ch + 1)->isLowSurrogate()) ++ch; } if (emoji) break; } diff --git a/Telegram/SourceFiles/gui/scrollarea.cpp b/Telegram/SourceFiles/gui/scrollarea.cpp index ac0a521d5..b8d90092b 100644 --- a/Telegram/SourceFiles/gui/scrollarea.cpp +++ b/Telegram/SourceFiles/gui/scrollarea.cpp @@ -94,7 +94,7 @@ void ScrollBar::updateBar(bool force) { } if (newBar != _bar) { _bar = newBar; - update(); + parentWidget()->update(geometry()); } if (_vertical) { bool newTopSh = (_st->topsh < 0) || (_area->scrollTop() > _st->topsh), newBottomSh = (_st->bottomsh < 0) || (_area->scrollTop() < _area->scrollTopMax() - _st->bottomsh); @@ -143,7 +143,7 @@ bool ScrollBar::animStep(float64 ms) { a_bg.update(dt, anim::linear); a_bar.update(dt, anim::linear); } - update(); + parentWidget()->update(geometry()); return res; } @@ -253,19 +253,16 @@ void ScrollBar::resizeEvent(QResizeEvent *e) { } ScrollArea::ScrollArea(QWidget *parent, const style::flatScroll &st, bool handleTouch) : QScrollArea(parent), -_st(st), +_disabled(false), _st(st), hor(this, false, &_st), vert(this, true, &_st), topSh(this, &_st), bottomSh(this, &_st), _touchEnabled(handleTouch), _touchScroll(false), _touchPress(false), _touchRightButton(false), _touchScrollState(TouchScrollManual), _touchPrevPosValid(false), _touchWaitingAcceleration(false), _touchSpeedTime(0), _touchAccelerationTime(0), _touchTime(0), _widgetAcceptsTouch(false) { - connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SIGNAL(scrolled())); - connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SIGNAL(scrolled())); + connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(onScrolled())); + connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(onScrolled())); connect(&vert, SIGNAL(topShadowVisibility(bool)), &topSh, SLOT(changeVisibility(bool))); connect(&vert, SIGNAL(bottomShadowVisibility(bool)), &bottomSh, SLOT(changeVisibility(bool))); vert.updateBar(true); - if (_st.hiding) { - connect(this, SIGNAL(scrolled()), this, SLOT(onScrolled())); - } setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -292,19 +289,31 @@ void ScrollArea::touchDeaccelerate(int32 elapsed) { } void ScrollArea::onScrolled() { + bool em = false; int32 horValue = horizontalScrollBar()->value(), vertValue = verticalScrollBar()->value(); if (_horValue != horValue) { - _horValue = horValue; - if (_st.hiding) { - hor.hideTimeout(_st.hiding); + if (_disabled) { + horizontalScrollBar()->setValue(_horValue); + } else { + _horValue = horValue; + if (_st.hiding) { + hor.hideTimeout(_st.hiding); + } + em = true; } } if (_vertValue != vertValue) { - _vertValue = vertValue; - if (_st.hiding) { - vert.hideTimeout(_st.hiding); + if (_disabled) { + verticalScrollBar()->setValue(_vertValue); + } else { + _vertValue = vertValue; + if (_st.hiding) { + vert.hideTimeout(_st.hiding); + } + em = true; } } + if (em) emit scrolled(); } int ScrollArea::scrollWidth() const { @@ -528,6 +537,21 @@ void ScrollArea::touchScrollUpdated(const QPoint &screenPos) { touchUpdateSpeed(); } +void ScrollArea::disableScroll(bool dis) { + _disabled = dis; + if (_disabled) { + hor.hideTimeout(0); + vert.hideTimeout(0); + } +} + +void ScrollArea::scrollContentsBy(int dx, int dy) { + if (_disabled) { + return; + } + QScrollArea::scrollContentsBy(dx, dy); +} + bool ScrollArea::touchScroll(const QPoint &delta) { int32 scTop = scrollTop(), scMax = scrollTopMax(), scNew = snap(scTop - delta.y(), 0, scMax); if (scNew == scTop) return false; @@ -559,6 +583,7 @@ void ScrollArea::keyPressEvent(QKeyEvent *e) { } void ScrollArea::enterEvent(QEvent *e) { + if (_disabled) return; if (_st.hiding) { hor.hideTimeout(_st.hiding); vert.hideTimeout(_st.hiding); diff --git a/Telegram/SourceFiles/gui/scrollarea.h b/Telegram/SourceFiles/gui/scrollarea.h index 6e3580511..f12fb6bf5 100644 --- a/Telegram/SourceFiles/gui/scrollarea.h +++ b/Telegram/SourceFiles/gui/scrollarea.h @@ -134,6 +134,7 @@ public: public slots: void scrollToY(int toTop, int toBottom = -1); + void disableScroll(bool dis); void onScrolled(); void onTouchTimer(); @@ -146,6 +147,10 @@ signals: void scrollFinished(); void geometryChanged(); +protected: + + void scrollContentsBy(int dx, int dy); + private: bool touchScroll(const QPoint &delta); @@ -156,6 +161,8 @@ private: void touchUpdateSpeed(); void touchDeaccelerate(int32 elapsed); + bool _disabled; + style::flatScroll _st; ScrollBar hor, vert; ScrollShadow topSh, bottomSh; diff --git a/Telegram/SourceFiles/gui/text.cpp b/Telegram/SourceFiles/gui/text.cpp index 2082b3732..338d4e42e 100644 --- a/Telegram/SourceFiles/gui/text.cpp +++ b/Telegram/SourceFiles/gui/text.cpp @@ -240,20 +240,25 @@ const QChar *textSkipCommand(const QChar *from, const QChar *end, bool canLink) } QString textEmojiString(EmojiPtr emoji) { + if ((emoji->code & 0xFFFF0000U) == 0xFFFF0000U) { // sequence + return emojiGetSequence(emoji->code & 0xFFFFU); + } + QString result; result.reserve(emoji->len + (emoji->postfix ? 1 : 0)); - switch (emoji->len) { - case 1: result.append(QChar(emoji->code & 0xFFFF)); break; - case 2: + if (!(emoji->code >> 16)) { + result.append(QChar(emoji->code & 0xFFFF)); + } else { result.append(QChar((emoji->code >> 16) & 0xFFFF)); result.append(QChar(emoji->code & 0xFFFF)); - break; - case 4: - result.append(QChar((emoji->code >> 16) & 0xFFFF)); - result.append(QChar(emoji->code & 0xFFFF)); - result.append(QChar((emoji->code2 >> 16) & 0xFFFF)); - result.append(QChar(emoji->code2 & 0xFFFF)); - break; + if (emoji->code2) { + result.append(QChar((emoji->code2 >> 16) & 0xFFFF)); + result.append(QChar(emoji->code2 & 0xFFFF)); + } + } + if (emoji->color && ((emoji->color & 0xFFFF0000U) != 0xFFFF0000U)) { + result.append(QChar((emoji->color >> 16) & 0xFFFF)); + result.append(QChar(emoji->color & 0xFFFF)); } if (emoji->postfix) result.append(QChar(emoji->postfix)); return result; @@ -515,24 +520,15 @@ public: } void parseEmojiFromCurrent() { - const EmojiData *e = getEmoji(chInt); + int len = 0, skipped = (chInt > 0xFFFFU) ? 1 : 0; + EmojiPtr e = emojiFromText(ptr - skipped, end, len); if (!e) return; - if (e->len > 2) { - if (ptr + 2 >= end || e->code2 != ((uint32((ptr + 1)->unicode()) << 16) | uint32((ptr + 2)->unicode()))) { - return; - } else { - _t->_text.push_back(*++ptr); - _t->_text.push_back(*++ptr); - } - } - int emojiLen = e->len; - if (ptr + 1 < end && (ptr + 1)->unicode() == 0xFE0F) { + for (int l = len - skipped - 1; l > 0; --l) { _t->_text.push_back(*++ptr); - ++emojiLen; } - createBlock(-emojiLen); + createBlock(-len); emoji = e; } @@ -1323,7 +1319,7 @@ public: } } } - _p->drawPixmap(QPoint((glyphX + int(st::emojiPadding)).toInt(), _y + _yDelta + emojiY), App::emojis(), QRect(static_cast(currentBlock)->emoji->x, static_cast(currentBlock)->emoji->y, st::emojiImgSize, st::emojiImgSize)); + emojiDraw(*_p, static_cast(currentBlock)->emoji, (glyphX + int(st::emojiPadding)).toInt(), _y + _yDelta + emojiY); // } else if (_p && currentBlock->type() == TextBlockSkip) { // debug // _p->fillRect(QRect(x.toInt(), _y, currentBlock->width(), static_cast(currentBlock)->height()), QColor(0, 0, 0, 32)); } @@ -4047,29 +4043,19 @@ bool textSplit(QString &sendingText, QString &leftText, int32 limit) { } } } - EmojiPtr e = 0; - if (ch->isHighSurrogate()) { - if (ch + 1 < end && (ch + 1)->isLowSurrogate()) { - e = getEmoji((ch->unicode() << 16) | (ch + 1)->unicode()); - if (!e) { - ++ch; - } - } - } else { - if (ch + 1 < end) { - if (((ch->unicode() >= 48 && ch->unicode() < 58) || ch->unicode() == 35) && (ch + 1)->unicode() == 0x20E3) { - e = getEmoji((ch->unicode() << 16) | (ch + 1)->unicode()); - } else if ((ch + 1)->unicode() == 0xFE0F) { - e = getEmoji(ch->unicode()); - } - } - } + int elen = 0; + EmojiPtr e = emojiFromText(ch, end, elen); if (e) { - ch += (e->len - 1); - if (ch + 1 < end && (ch + 1)->unicode() == 0xFE0F) { - ++ch; - ++s; + for (int i = 0; i < elen; ++i, ++ch, ++s) { + if (ch->isHighSurrogate() && i + 1 < elen && (ch + 1)->isLowSurrogate()) { + ++ch; + ++i; + } } + --ch; + --s; + } else if (ch->isHighSurrogate() && ch + 1 < end && (ch + 1)->isLowSurrogate()) { + ++ch; } if (s >= limit) { sendingText = leftText.mid(0, good - start); @@ -4247,3 +4233,7 @@ LinkRanges textParseLinks(const QString &text, int32 flags, bool rich) { // some return lnkRanges; } + +void emojiDraw(QPainter &p, EmojiPtr e, int x, int y) { + p.drawPixmap(QPoint(x, y), App::emojis(), QRect(e->x * ESize, e->y * ESize, ESize, ESize)); +} diff --git a/Telegram/SourceFiles/gui/text.h b/Telegram/SourceFiles/gui/text.h index 0de70f355..49c44c75d 100644 --- a/Telegram/SourceFiles/gui/text.h +++ b/Telegram/SourceFiles/gui/text.h @@ -49,6 +49,8 @@ LinkRanges textParseLinks(const QString &text, int32 flags, bool rich = false); #include "gui/emoji_config.h" +void emojiDraw(QPainter &p, EmojiPtr e, int x, int y); + #include "../../../QtStatic/qtbase/src/gui/text/qfontengine_p.h" enum TextBlockType { diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 888a0a468..3e3c34508 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -3311,9 +3311,9 @@ void HistoryWebPage::draw(QPainter &p, const HistoryItem *parent, bool selected, p.fillRect(QRect(width - pixwidth, 0, pixwidth, pixheight), st::black->b); } if (_pixw > pixwidth) { - p.drawPixmap(QRect(width - pixwidth, (pixheight - _pixh) / 2, pixwidth, _pixh), pix, QRect((_pixw - pixwidth) / 2, 0, pixwidth, _pixh)); + p.drawPixmap(QRect(width - pixwidth, (pixheight - _pixh) / 2, pixwidth, _pixh), pix, QRect(cIntRetinaFactor() * (_pixw - pixwidth) / 2, 0, cIntRetinaFactor() * pixwidth, cIntRetinaFactor() * _pixh)); } else if (_pixh > pixheight) { - p.drawPixmap(QRect(width - pixwidth + (pixwidth - _pixw) / 2, 0, _pixw, pixheight), pix, QRect(0, (_pixh - pixheight) / 2, _pixw, pixheight)); + p.drawPixmap(QRect(width - pixwidth + (pixwidth - _pixw) / 2, 0, _pixw, pixheight), pix, QRect(0, cIntRetinaFactor() * (_pixh - pixheight) / 2, cIntRetinaFactor() * _pixw, cIntRetinaFactor() * pixheight)); } else { p.drawPixmap(QPoint(width - pixwidth + (pixwidth - _pixw) / 2, (pixheight - _pixh) / 2), pix); } @@ -4375,6 +4375,14 @@ void HistoryMessage::initDimensions(const QString &text) { if (_media) { _text.setText(st::msgFont, text, _historyTextOptions); } else { +/* char tmp[64] = {0}, tmp2[64] = {0}; + int from = 0, to = 65535; + QString a = QString::fromLatin1(hashMd5Hex(hashMd5(text.constData() + qMin(text.size(), from), (qMin(text.size(), to) - qMin(text.size(), from)) * 2, tmp2), tmp)); + QString b; + for (int i = qMin(text.size(), from); i < qMin(text.size(), to); ++i) { + b.append(QString("0x%1 ").arg(text.at(i).unicode(), 0, 16)); + } + _text.setText(st::msgFont, text.mid(from, to - from) + ' ' + b + a + textcmdSkipBlock(_timeWidth, st::msgDateFont->height - st::msgDateDelta.y()), _historyTextOptions);*/ _text.setText(st::msgFont, text + textcmdSkipBlock(_timeWidth, st::msgDateFont->height - st::msgDateDelta.y()), _historyTextOptions); } } diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index 6e57dce46..38f507eaa 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -1763,9 +1763,7 @@ void HistoryWidget::updateTyping(bool typing) { //} void HistoryWidget::updateRecentStickers() { - if (cEmojiTab() == dbietStickers) { - _emojiPan.onTabChange(); - } + _emojiPan.refreshStickers(); } void HistoryWidget::typingDone(const MTPBool &result, mtpRequestId req) { @@ -1852,7 +1850,6 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) { } cSetRecentStickers(add); Local::writeRecentStickers(); - _emojiPan.onTabChange(); } const QVector &packs(d.vpacks.c_vector().v); @@ -1862,23 +1859,11 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) { QString emoticon(qs(p.vemoticon)); EmojiPtr e = 0; for (const QChar *ch = emoticon.constData(), *end = emoticon.constEnd(); ch != end; ++ch) { - if (ch->isHighSurrogate()) { - if (ch + 1 < end && (ch + 1)->isLowSurrogate()) { - e = getEmoji((ch->unicode() << 16) | (ch + 1)->unicode()); - if (!e) { - ++ch; - } - } - } else { - if (ch + 1 < end) { - if (((ch->unicode() >= 48 && ch->unicode() < 58) || ch->unicode() == 35) && (ch + 1)->unicode() == 0x20E3) { - e = getEmoji((ch->unicode() << 16) | (ch + 1)->unicode()); - } else if ((ch + 1)->unicode() == 0xFE0F) { - e = getEmoji(ch->unicode()); - } - } - } + int len = 0; + e = emojiFromText(ch, end, len); if (e) break; + + if (ch + 1 < end && ch->isHighSurrogate() && (ch + 1)->isLowSurrogate()) ++ch; } if (e) { const QVector docs(p.vdocuments.c_vector().v); @@ -1912,7 +1897,7 @@ void HistoryWidget::stickersGot(const MTPmessages_AllStickers &stickers) { } // updateStickerPan(); - _emojiPan.onTabChange(); + _emojiPan.refreshStickers(); } } diff --git a/Telegram/SourceFiles/localstorage.cpp b/Telegram/SourceFiles/localstorage.cpp index e6684cd4d..e69c76a65 100644 --- a/Telegram/SourceFiles/localstorage.cpp +++ b/Telegram/SourceFiles/localstorage.cpp @@ -905,24 +905,62 @@ namespace { if (!_checkStreamStatus(stream)) return false; switch (v) { - case dbietRecent: cSetEmojiTab(dbietRecent); break; - case dbietPeople: cSetEmojiTab(dbietPeople); break; - case dbietNature: cSetEmojiTab(dbietNature); break; - case dbietObjects: cSetEmojiTab(dbietObjects); break; - case dbietPlaces: cSetEmojiTab(dbietPlaces); break; - case dbietSymbols: cSetEmojiTab(dbietSymbols); break; - case dbietStickers: cSetEmojiTab(dbietStickers); break; + case dbietRecent : cSetEmojiTab(dbietRecent ); break; + case dbietPeople : cSetEmojiTab(dbietPeople ); break; + case dbietNature : cSetEmojiTab(dbietNature ); break; + case dbietFood : cSetEmojiTab(dbietFood ); break; + case dbietCelebration: cSetEmojiTab(dbietCelebration); break; + case dbietActivity : cSetEmojiTab(dbietActivity ); break; + case dbietTravel : cSetEmojiTab(dbietTravel ); break; + case dbietObjects : cSetEmojiTab(dbietObjects ); break; + case dbietStickers : cSetEmojiTab(dbietStickers ); break; + } + } break; + + case dbiRecentEmojisOld: { + RecentEmojisPreloadOld v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + if (!v.isEmpty()) { + RecentEmojisPreload p; + p.reserve(v.size()); + for (int i = 0; i < v.size(); ++i) { + uint64 e(v.at(i).first); + switch (e) { + case 0xD83CDDEFLLU: e = 0xD83CDDEFD83CDDF5LLU; break; + case 0xD83CDDF0LLU: e = 0xD83CDDF0D83CDDF7LLU; break; + case 0xD83CDDE9LLU: e = 0xD83CDDE9D83CDDEALLU; break; + case 0xD83CDDE8LLU: e = 0xD83CDDE8D83CDDF3LLU; break; + case 0xD83CDDFALLU: e = 0xD83CDDFAD83CDDF8LLU; break; + case 0xD83CDDEBLLU: e = 0xD83CDDEBD83CDDF7LLU; break; + case 0xD83CDDEALLU: e = 0xD83CDDEAD83CDDF8LLU; break; + case 0xD83CDDEELLU: e = 0xD83CDDEED83CDDF9LLU; break; + case 0xD83CDDF7LLU: e = 0xD83CDDF7D83CDDFALLU; break; + case 0xD83CDDECLLU: e = 0xD83CDDECD83CDDE7LLU; break; + } + p.push_back(qMakePair(e, v.at(i).second)); + } + cSetRecentEmojisPreload(p); } } break; case dbiRecentEmojis: { - RecentEmojiPreload v; + RecentEmojisPreload v; stream >> v; if (!_checkStreamStatus(stream)) return false; cSetRecentEmojisPreload(v); } break; + case dbiEmojiVariants: { + EmojiColorVariants v; + stream >> v; + if (!_checkStreamStatus(stream)) return false; + + cSetEmojiVariants(v); + } break; + case dbiDialogLastPath: { QString path; stream >> path; @@ -1140,7 +1178,8 @@ namespace { uint32 size = 11 * (sizeof(quint32) + sizeof(qint32)); size += sizeof(quint32) + _stringSize(cAskDownloadPath() ? QString() : cDownloadPath()); - size += sizeof(quint32) + sizeof(qint32) + cGetRecentEmojis().size() * (sizeof(uint32) + sizeof(ushort)); + size += sizeof(quint32) + sizeof(qint32) + cGetRecentEmojis().size() * (sizeof(uint64) + sizeof(ushort)); + size += sizeof(quint32) + sizeof(qint32) + cEmojiVariants().size() * (sizeof(uint32) + sizeof(uint64)); size += sizeof(quint32) + _stringSize(cDialogLastPath()); EncryptedDescriptor data(size); @@ -1158,13 +1197,15 @@ namespace { data.stream << quint32(dbiEmojiTab) << qint32(cEmojiTab()); data.stream << quint32(dbiDialogLastPath) << cDialogLastPath(); - RecentEmojiPreload v; + RecentEmojisPreload v; v.reserve(cGetRecentEmojis().size()); for (RecentEmojiPack::const_iterator i = cGetRecentEmojis().cbegin(), e = cGetRecentEmojis().cend(); i != e; ++i) { - v.push_back(qMakePair(i->first->code, i->second)); + v.push_back(qMakePair(emojiKey(i->first), i->second)); } data.stream << quint32(dbiRecentEmojis) << v; + data.stream << quint32(dbiEmojiVariants) << cEmojiVariants(); + FileWriteDescriptor file(_userSettingsKey); file.writeEncrypted(data); } diff --git a/Telegram/SourceFiles/settings.cpp b/Telegram/SourceFiles/settings.cpp index ae1da9cc7..c0a09c051 100644 --- a/Telegram/SourceFiles/settings.cpp +++ b/Telegram/SourceFiles/settings.cpp @@ -86,7 +86,8 @@ bool gHasPasscode = false; DBIEmojiTab gEmojiTab = dbietRecent; RecentEmojiPack gRecentEmojis; -RecentEmojiPreload gRecentEmojisPreload; +RecentEmojisPreload gRecentEmojisPreload; +EmojiColorVariants gEmojiVariants; AllStickers gStickers; QByteArray gStickersHash; @@ -185,22 +186,22 @@ void settingsParseArgs(int argc, char *argv[]) { } } -const RecentEmojiPack &cGetRecentEmojis() { +RecentEmojiPack &cGetRecentEmojis() { if (cRecentEmojis().isEmpty()) { RecentEmojiPack r; if (!cRecentEmojisPreload().isEmpty()) { - RecentEmojiPreload p(cRecentEmojisPreload()); - cSetRecentEmojisPreload(RecentEmojiPreload()); + RecentEmojisPreload p(cRecentEmojisPreload()); + cSetRecentEmojisPreload(RecentEmojisPreload()); r.reserve(p.size()); - for (RecentEmojiPreload::const_iterator i = p.cbegin(), e = p.cend(); i != e; ++i) { - uint32 code = ((i->first & 0xFFFFU) == 0xFE0FU) ? ((i->first >> 16) & 0xFFFFU) : i->first; - EmojiPtr ep(getEmoji(code)); + for (RecentEmojisPreload::const_iterator i = p.cbegin(), e = p.cend(); i != e; ++i) { + uint64 code = ((!(i->first & 0xFFFFFFFF00000000LLU) && (i->first & 0xFFFFU) == 0xFE0FU)) ? ((i->first >> 16) & 0xFFFFU) : i->first; + EmojiPtr ep(emojiFromKey(code)); if (!ep) continue; if (ep->postfix) { int32 j = 0, l = r.size(); for (; j < l; ++j) { - if (r[j].first->code == code) { + if (emojiKey(r[j].first) == code) { break; } } @@ -211,47 +212,47 @@ const RecentEmojiPack &cGetRecentEmojis() { r.push_back(qMakePair(ep, i->second)); } } - uint32 defaultRecent[] = { - 0xD83DDE02U, - 0xD83DDE18U, - 0x2764U, - 0xD83DDE0DU, - 0xD83DDE0AU, - 0xD83DDE01U, - 0xD83DDC4DU, - 0x263AU, - 0xD83DDE14U, - 0xD83DDE04U, - 0xD83DDE2DU, - 0xD83DDC8BU, - 0xD83DDE12U, - 0xD83DDE33U, - 0xD83DDE1CU, - 0xD83DDE48U, - 0xD83DDE09U, - 0xD83DDE03U, - 0xD83DDE22U, - 0xD83DDE1DU, - 0xD83DDE31U, - 0xD83DDE21U, - 0xD83DDE0FU, - 0xD83DDE1EU, - 0xD83DDE05U, - 0xD83DDE1AU, - 0xD83DDE4AU, - 0xD83DDE0CU, - 0xD83DDE00U, - 0xD83DDE0BU, - 0xD83DDE06U, - 0xD83DDC4CU, - 0xD83DDE10U, - 0xD83DDE15U, + uint64 defaultRecent[] = { + 0xD83DDE02LLU, + 0xD83DDE18LLU, + 0x2764LLU, + 0xD83DDE0DLLU, + 0xD83DDE0ALLU, + 0xD83DDE01LLU, + 0xD83DDC4DLLU, + 0x263ALLU, + 0xD83DDE14LLU, + 0xD83DDE04LLU, + 0xD83DDE2DLLU, + 0xD83DDC8BLLU, + 0xD83DDE12LLU, + 0xD83DDE33LLU, + 0xD83DDE1CLLU, + 0xD83DDE48LLU, + 0xD83DDE09LLU, + 0xD83DDE03LLU, + 0xD83DDE22LLU, + 0xD83DDE1DLLU, + 0xD83DDE31LLU, + 0xD83DDE21LLU, + 0xD83DDE0FLLU, + 0xD83DDE1ELLU, + 0xD83DDE05LLU, + 0xD83DDE1ALLU, + 0xD83DDE4ALLU, + 0xD83DDE0CLLU, + 0xD83DDE00LLU, + 0xD83DDE0BLLU, + 0xD83DDE06LLU, + 0xD83DDC4CLLU, + 0xD83DDE10LLU, + 0xD83DDE15LLU, }; for (int32 i = 0, s = sizeof(defaultRecent) / sizeof(defaultRecent[0]); i < s; ++i) { if (r.size() >= EmojiPadPerRow * EmojiPadRowsPerPage) break; - EmojiPtr ep(getEmoji(defaultRecent[i])); - if (!ep) continue; + EmojiPtr ep(emojiGet(defaultRecent[i])); + if (!ep || ep == TwoSymbolEmoji) continue; int32 j = 0, l = r.size(); for (; j < l; ++j) { @@ -265,5 +266,5 @@ const RecentEmojiPack &cGetRecentEmojis() { } cSetRecentEmojis(r); } - return cRecentEmojis(); + return cRefRecentEmojis(); } diff --git a/Telegram/SourceFiles/settings.h b/Telegram/SourceFiles/settings.h index 8023fc410..b63480b9d 100644 --- a/Telegram/SourceFiles/settings.h +++ b/Telegram/SourceFiles/settings.h @@ -41,6 +41,11 @@ inline void cSet##Name(const Type &Name) { \ g##Name = Name; \ } +#define DeclareRefSetting(Type, Name) DeclareSetting(Type, Name) \ +inline Type &cRef##Name() { \ + return g##Name; \ +} + DeclareSetting(bool, Rtl); DeclareSetting(Qt::LayoutDirection, LangDir); inline bool rtl() { @@ -166,12 +171,15 @@ typedef const EmojiData *EmojiPtr; static EmojiPtr TwoSymbolEmoji = EmojiPtr(0x01); typedef QVector EmojiPack; -typedef QVector > RecentEmojiPreload; +typedef QVector > RecentEmojisPreloadOld; +typedef QVector > RecentEmojisPreload; typedef QVector > RecentEmojiPack; -DeclareSetting(RecentEmojiPack, RecentEmojis); -DeclareSetting(RecentEmojiPreload, RecentEmojisPreload); +typedef QMap EmojiColorVariants; +DeclareRefSetting(RecentEmojiPack, RecentEmojis); +DeclareSetting(RecentEmojisPreload, RecentEmojisPreload); +DeclareRefSetting(EmojiColorVariants, EmojiVariants); -const RecentEmojiPack &cGetRecentEmojis(); +RecentEmojiPack &cGetRecentEmojis(); struct DocumentData; typedef QVector StickerPack; diff --git a/Telegram/SourceFiles/telegram.qrc b/Telegram/SourceFiles/telegram.qrc index e15d6acaf..18b9a5325 100644 --- a/Telegram/SourceFiles/telegram.qrc +++ b/Telegram/SourceFiles/telegram.qrc @@ -10,10 +10,11 @@ art/sprite_125x.png art/sprite_150x.png art/sprite_200x.png - art/emoji.png - art/emoji_125x.png - art/emoji_150x.png - art/emoji_200x.png + art/emoji.webp + art/emoji_125x.webp + art/emoji_150x.webp + art/emoji_200x.webp + art/emoji_250x.webp art/blank.gif art/icon256.png art/iconbig256.png diff --git a/Telegram/SourceFiles/telegram_linux.qrc b/Telegram/SourceFiles/telegram_linux.qrc index d0b331619..8083a827e 100644 --- a/Telegram/SourceFiles/telegram_linux.qrc +++ b/Telegram/SourceFiles/telegram_linux.qrc @@ -10,10 +10,11 @@ art/sprite_125x.png art/sprite_150x.png art/sprite_200x.png - art/emoji.png - art/emoji_125x.png - art/emoji_150x.png - art/emoji_200x.png + art/emoji.webp + art/emoji_125x.webp + art/emoji_150x.webp + art/emoji_200x.webp + art/emoji_250x.webp art/blank.gif art/icon256.png diff --git a/Telegram/SourceFiles/types.h b/Telegram/SourceFiles/types.h index b1cf6516d..e72fbd381 100644 --- a/Telegram/SourceFiles/types.h +++ b/Telegram/SourceFiles/types.h @@ -249,7 +249,7 @@ enum DataBlockId { dbiDownloadPath = 21, dbiScale = 22, dbiEmojiTab = 23, - dbiRecentEmojis = 24, + dbiRecentEmojisOld = 24, dbiLoggedPhoneNumber = 25, dbiMutedPeers = 26, // 27 reserved @@ -261,6 +261,8 @@ enum DataBlockId { dbiTileBackground = 33, dbiAutoLock = 34, dbiDialogLastPath = 35, + dbiRecentEmojis = 36, + dbiEmojiVariants = 37, dbiEncryptedWithSalt = 333, dbiEncrypted = 444, @@ -317,13 +319,6 @@ enum DBIScale { dbisScaleCount = 5, }; -writeEmojiCategory(tcpp, emojiCategory1, sizeof(emojiCategory1) / sizeof(emojiCategory1[0]), "People"); -writeEmojiCategory(tcpp, emojiCategory2, sizeof(emojiCategory2) / sizeof(emojiCategory2[0]), "Nature"); -writeEmojiCategory(tcpp, emojiCategory3, sizeof(emojiCategory3) / sizeof(emojiCategory3[0]), "Food"); -writeEmojiCategory(tcpp, emojiCategory4, sizeof(emojiCategory4) / sizeof(emojiCategory4[0]), "Celebration"); -writeEmojiCategory(tcpp, emojiCategory5, sizeof(emojiCategory5) / sizeof(emojiCategory5[0]), "Activity"); -writeEmojiCategory(tcpp, emojiCategory6, sizeof(emojiCategory6) / sizeof(emojiCategory6[0]), "Travel"); -writeEmojiCategory(tcpp, emojiCategory7, sizeof(emojiCategory7) / sizeof(emojiCategory7[0]), "Objects"); enum DBIEmojiTab { dbietRecent = -1, dbietPeople = 0, @@ -335,6 +330,11 @@ enum DBIEmojiTab { dbietObjects = 6, dbietStickers = 666, }; +static const int emojiTabCount = 8; +static const int emojiTabShift = 100000; +inline DBIEmojiTab emojiTabAtIndex(int index) { + return (index < 0 || index >= emojiTabCount) ? dbietRecent : DBIEmojiTab(index - 1); +} enum DBIPlatform { dbipWindows = 0, diff --git a/Telegram/Telegram.xcodeproj/qt_preprocess.mak b/Telegram/Telegram.xcodeproj/qt_preprocess.mak index e774b7941..18daacb89 100644 --- a/Telegram/Telegram.xcodeproj/qt_preprocess.mak +++ b/Telegram/Telegram.xcodeproj/qt_preprocess.mak @@ -61,18 +61,19 @@ compiler_rcc_make_all: GeneratedFiles/qrc_telegram.cpp compiler_rcc_clean: -$(DEL_FILE) GeneratedFiles/qrc_telegram.cpp GeneratedFiles/qrc_telegram.cpp: SourceFiles/telegram.qrc \ - SourceFiles/art/emoji.png \ + SourceFiles/art/emoji.webp \ SourceFiles/art/blank.gif \ SourceFiles/art/bg.jpg \ SourceFiles/art/sprite_150x.png \ SourceFiles/art/sprite.png \ SourceFiles/art/icon256.png \ - SourceFiles/art/emoji_150x.png \ + SourceFiles/art/emoji_150x.webp \ SourceFiles/art/sprite_200x.png \ SourceFiles/art/newmsg.wav \ SourceFiles/art/sprite_125x.png \ - SourceFiles/art/emoji_200x.png \ - SourceFiles/art/emoji_125x.png \ + SourceFiles/art/emoji_200x.webp \ + SourceFiles/art/emoji_250x.webp \ + SourceFiles/art/emoji_125x.webp \ SourceFiles/art/fonts/OpenSans-Regular.ttf \ SourceFiles/art/fonts/OpenSans-Bold.ttf \ SourceFiles/art/fonts/OpenSans-Semibold.ttf \