diff options
17 files changed, 1633 insertions, 584 deletions
diff --git a/indra/llui/llemojidictionary.cpp b/indra/llui/llemojidictionary.cpp index 179c5d25bf..cdcf5a93d6 100644 --- a/indra/llui/llemojidictionary.cpp +++ b/indra/llui/llemojidictionary.cpp @@ -38,7 +38,12 @@ // Constants // -constexpr char SKINNED_EMOJI_FILENAME[] = "emoji_characters.xml"; +static const std::string SKINNED_EMOJI_FILENAME("emoji_characters.xml"); +static const std::string SKINNED_CATEGORY_FILENAME("emoji_categories.xml"); +static const std::string COMMON_GROUP_FILENAME("emoji_groups.xml"); +static const std::string GROUP_NAME_ALL("all"); +static const std::string GROUP_NAME_OTHERS("others"); +static const std::string GROUP_NAME_SKIP("skip"); // ============================================================================ // Helper functions @@ -50,82 +55,68 @@ std::list<T> llsd_array_to_list(const LLSD& sd, std::function<void(T&)> mutator template<> std::list<std::string> llsd_array_to_list(const LLSD& sd, std::function<void(std::string&)> mutator) { - std::list<std::string> result; - for (LLSD::array_const_iterator it = sd.beginArray(), end = sd.endArray(); it != end; ++it) - { - const LLSD& entry = *it; - if (!entry.isString()) - continue; - - result.push_back(entry.asStringRef()); - if (mutator) - { - mutator(result.back()); - } - } - return result; -} - -LLEmojiDescriptor::LLEmojiDescriptor(const LLSD& descriptor_sd) -{ - Name = descriptor_sd["Name"].asStringRef(); - - const LLWString emoji_string = utf8str_to_wstring(descriptor_sd["Character"].asString()); - Character = (1 == emoji_string.size()) ? emoji_string[0] : L'\0'; // We don't currently support character composition - - auto toLower = [](std::string& str) { LLStringUtil::toLower(str); }; - ShortCodes = llsd_array_to_list<std::string>(descriptor_sd["ShortCodes"], toLower); - Categories = llsd_array_to_list<std::string>(descriptor_sd["Categories"], toLower); - - if (Name.empty()) - { - Name = ShortCodes.front(); - } -} - -bool LLEmojiDescriptor::isValid() const -{ - return - Character && - !ShortCodes.empty() && - !Categories.empty(); + std::list<std::string> result; + for (LLSD::array_const_iterator it = sd.beginArray(), end = sd.endArray(); it != end; ++it) + { + const LLSD& entry = *it; + if (!entry.isString()) + continue; + + result.push_back(entry.asStringRef()); + if (mutator) + { + mutator(result.back()); + } + } + return result; } struct emoji_filter_base { - emoji_filter_base(const std::string& needle) - { - // Search without the colon (if present) so the user can type ':food' and see all emojis in the 'Food' category - mNeedle = (boost::starts_with(needle, ":")) ? needle.substr(1) : needle; - LLStringUtil::toLower(mNeedle); - } + emoji_filter_base(const std::string& needle) + { + // Search without the colon (if present) so the user can type ':food' and see all emojis in the 'Food' category + mNeedle = (boost::starts_with(needle, ":")) ? needle.substr(1) : needle; + LLStringUtil::toLower(mNeedle); + } protected: - std::string mNeedle; + std::string mNeedle; }; struct emoji_filter_shortcode_or_category_contains : public emoji_filter_base { - emoji_filter_shortcode_or_category_contains(const std::string& needle) : emoji_filter_base(needle) {} + emoji_filter_shortcode_or_category_contains(const std::string& needle) : emoji_filter_base(needle) {} - bool operator()(const LLEmojiDescriptor& descr) const - { - for (const auto& short_code : descr.ShortCodes) - { - if (boost::icontains(short_code, mNeedle)) - return true; - } + bool operator()(const LLEmojiDescriptor& descr) const + { + for (const auto& short_code : descr.ShortCodes) + { + if (boost::icontains(short_code, mNeedle)) + return true; + } - for (const auto& category : descr.Categories) - { - if (boost::icontains(category, mNeedle)) - return true; - } + if (boost::icontains(descr.Category, mNeedle)) + return true; - return false; - } + return false; + } }; +std::string LLEmojiDescriptor::getShortCodes() const +{ + std::string result; + for (const std::string& shortCode : ShortCodes) + { + if (!result.empty()) + { + result += ", "; + } + result += shortCode; + } + return result; +} + // ============================================================================ // LLEmojiDictionary class // @@ -137,91 +128,278 @@ LLEmojiDictionary::LLEmojiDictionary() // static void LLEmojiDictionary::initClass() { - LLEmojiDictionary* pThis = &LLEmojiDictionary::initParamSingleton(); - - LLSD data; - - auto filenames = gDirUtilp->findSkinnedFilenames(LLDir::XUI, SKINNED_EMOJI_FILENAME, LLDir::CURRENT_SKIN); - if (filenames.empty()) - { - LL_WARNS() << "Emoji file characters not found" << LL_ENDL; - return; - } - const std::string filename = filenames.back(); - llifstream file(filename.c_str()); - if (file.is_open()) - { - LL_DEBUGS() << "Loading emoji characters file at " << filename << LL_ENDL; - LLSDSerialize::fromXML(data, file); - } - - if (data.isUndefined()) - { - LL_WARNS() << "Emoji file characters missing or ill-formed" << LL_ENDL; - return; - } - - for (LLSD::array_const_iterator descriptor_it = data.beginArray(), descriptor_end = data.endArray(); descriptor_it != descriptor_end; ++descriptor_it) - { - LLEmojiDescriptor descriptor(*descriptor_it); - if (!descriptor.isValid()) - { - LL_WARNS() << "Skipping invalid emoji descriptor " << descriptor.Character << LL_ENDL; - continue; - } - pThis->addEmoji(std::move(descriptor)); - } + LLEmojiDictionary* pThis = &LLEmojiDictionary::initParamSingleton(); + + pThis->loadTranslations(); + pThis->loadGroups(); + pThis->loadEmojis(); } LLWString LLEmojiDictionary::findMatchingEmojis(const std::string& needle) const { - LLWString result; - boost::transform(mEmojis | boost::adaptors::filtered(emoji_filter_shortcode_or_category_contains(needle)), - std::back_inserter(result), [](const auto& descr) { return descr.Character; }); - return result; + LLWString result; + boost::transform(mEmojis | boost::adaptors::filtered(emoji_filter_shortcode_or_category_contains(needle)), + std::back_inserter(result), [](const auto& descr) { return descr.Character; }); + return result; } const LLEmojiDescriptor* LLEmojiDictionary::getDescriptorFromEmoji(llwchar emoji) const { - const auto it = mEmoji2Descr.find(emoji); - return (mEmoji2Descr.end() != it) ? it->second : nullptr; + const auto it = mEmoji2Descr.find(emoji); + return (mEmoji2Descr.end() != it) ? it->second : nullptr; } const LLEmojiDescriptor* LLEmojiDictionary::getDescriptorFromShortCode(const std::string& short_code) const { - const auto it = mShortCode2Descr.find(short_code); - return (mShortCode2Descr.end() != it) ? it->second : nullptr; + const auto it = mShortCode2Descr.find(short_code); + return (mShortCode2Descr.end() != it) ? it->second : nullptr; } std::string LLEmojiDictionary::getNameFromEmoji(llwchar ch) const { - const auto it = mEmoji2Descr.find(ch); - return (mEmoji2Descr.end() != it) ? it->second->Name : LLStringUtil::null; + const auto it = mEmoji2Descr.find(ch); + return (mEmoji2Descr.end() != it) ? it->second->ShortCodes.front() : LLStringUtil::null; } bool LLEmojiDictionary::isEmoji(llwchar ch) const { - // Currently used codes: A9,AE,203C,2049,2122,...,2B55,3030,303D,3297,3299,1F004,...,1FAF6 - if (ch == 0xA9 || ch == 0xAE || (ch >= 0x2000 && ch < 0x3300) || (ch >= 0x1F000 && ch < 0x20000)) - { - return mEmoji2Descr.find(ch) != mEmoji2Descr.end(); - } + // Currently used codes: A9,AE,203C,2049,2122,...,2B55,3030,303D,3297,3299,1F004,...,1FAF6 + if (ch == 0xA9 || ch == 0xAE || (ch >= 0x2000 && ch < 0x3300) || (ch >= 0x1F000 && ch < 0x20000)) + { + return mEmoji2Descr.find(ch) != mEmoji2Descr.end(); + } - return false; + return false; +} + +void LLEmojiDictionary::loadTranslations() +{ + std::vector<std::string> filenames = gDirUtilp->findSkinnedFilenames(LLDir::XUI, SKINNED_CATEGORY_FILENAME, LLDir::CURRENT_SKIN); + if (filenames.empty()) + { + LL_WARNS() << "Emoji file categories not found" << LL_ENDL; + return; + } + + const std::string filename = filenames.back(); + llifstream file(filename.c_str()); + if (!file.is_open()) + { + LL_WARNS() << "Emoji file categories failed to open" << LL_ENDL; + return; + } + + LL_DEBUGS() << "Loading emoji categories file at " << filename << LL_ENDL; + + LLSD data; + LLSDSerialize::fromXML(data, file); + if (data.isUndefined()) + { + LL_WARNS() << "Emoji file categories missing or ill-formed" << LL_ENDL; + return; + } + + // Register translations for all categories + for (LLSD::array_const_iterator it = data.beginArray(), end = data.endArray(); it != end; ++it) + { + const LLSD& sd = *it; + const std::string& name = sd["Name"].asStringRef(); + const std::string& category = sd["Category"].asStringRef(); + if (!name.empty() && !category.empty()) + { + mTranslations[name] = category; + } + else + { + LL_WARNS() << "Skipping invalid emoji category '" << name << "' => '" << category << "'" << LL_ENDL; + } + } +} + +void LLEmojiDictionary::loadGroups() +{ + const std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, COMMON_GROUP_FILENAME); + llifstream file(filename.c_str()); + if (!file.is_open()) + { + LL_WARNS() << "Emoji file groups failed to open" << LL_ENDL; + return; + } + + LL_DEBUGS() << "Loading emoji groups file at " << filename << LL_ENDL; + + LLSD data; + LLSDSerialize::fromXML(data, file); + if (data.isUndefined()) + { + LL_WARNS() << "Emoji file groups missing or ill-formed" << LL_ENDL; + return; + } + + mGroups.clear(); + // Add group "all" + mGroups.emplace_back(); + // https://www.compart.com/en/unicode/U+1F50D + mGroups.front().Character = 0x1F50D; + // https://www.compart.com/en/unicode/U+1F302 + llwchar iconOthers = 0x1F302; + + // Register all groups + for (LLSD::array_const_iterator it = data.beginArray(), end = data.endArray(); it != end; ++it) + { + const LLSD& sd = *it; + const std::string& name = sd["Name"].asStringRef(); + if (name == GROUP_NAME_ALL) + { + mGroups.front().Character = loadIcon(sd); + } + else if (name == GROUP_NAME_OTHERS) + { + iconOthers = loadIcon(sd); + } + else if (name == GROUP_NAME_SKIP) + { + mSkipCategories = loadCategories(sd); + translateCategories(mSkipCategories); + } + else + { + // Add new group + mGroups.emplace_back(); + LLEmojiGroup& group = mGroups.back(); + group.Character = loadIcon(sd); + group.Categories = loadCategories(sd); + translateCategories(group.Categories); + + for (const std::string& category : group.Categories) + { + mCategory2Group.insert(std::make_pair(category, &group)); + } + } + } + + // Add group "others" + mGroups.emplace_back(); + mGroups.back().Character = iconOthers; +} + +void LLEmojiDictionary::loadEmojis() +{ + std::vector<std::string> filenames = gDirUtilp->findSkinnedFilenames(LLDir::XUI, SKINNED_EMOJI_FILENAME, LLDir::CURRENT_SKIN); + if (filenames.empty()) + { + LL_WARNS() << "Emoji file characters not found" << LL_ENDL; + return; + } + + const std::string filename = filenames.back(); + llifstream file(filename.c_str()); + if (!file.is_open()) + { + LL_WARNS() << "Emoji file characters failed to open" << LL_ENDL; + return; + } + + LL_DEBUGS() << "Loading emoji characters file at " << filename << LL_ENDL; + + LLSD data; + LLSDSerialize::fromXML(data, file); + if (data.isUndefined()) + { + LL_WARNS() << "Emoji file characters missing or ill-formed" << LL_ENDL; + return; + } + + for (LLSD::array_const_iterator it = data.beginArray(), end = data.endArray(); it != end; ++it) + { + const LLSD& sd = *it; + + llwchar icon = loadIcon(sd); + if (!icon) + { + LL_WARNS() << "Skipping invalid emoji descriptor (no icon)" << LL_ENDL; + continue; + } + + std::list<std::string> categories = loadCategories(sd); + if (categories.empty()) + { + LL_WARNS() << "Skipping invalid emoji descriptor (no categories)" << LL_ENDL; + continue; + } + + std::string category = categories.front(); + + if (std::find(mSkipCategories.begin(), mSkipCategories.end(), category) != mSkipCategories.end()) + { + // This category is listed for skip + continue; + } + + std::list<std::string> shortCodes = loadShortCodes(sd); + if (shortCodes.empty()) + { + LL_WARNS() << "Skipping invalid emoji descriptor (no shortCodes)" << LL_ENDL; + continue; + } + + if (mCategory2Group.find(category) == mCategory2Group.end()) + { + // Add unknown category to "others" group + mGroups.back().Categories.push_back(category); + mCategory2Group.insert(std::make_pair(category, &mGroups.back())); + } + + mEmojis.emplace_back(); + LLEmojiDescriptor& emoji = mEmojis.back(); + emoji.Character = icon; + emoji.Category = category; + emoji.ShortCodes = std::move(shortCodes); + + mEmoji2Descr.insert(std::make_pair(icon, &emoji)); + mCategory2Descrs[category].push_back(&emoji); + for (const std::string& shortCode : emoji.ShortCodes) + { + mShortCode2Descr.insert(std::make_pair(shortCode, &emoji)); + } + } +} + +llwchar LLEmojiDictionary::loadIcon(const LLSD& sd) +{ + // We don't currently support character composition + const LLWString icon = utf8str_to_wstring(sd["Character"].asString()); + return (1 == icon.size()) ? icon[0] : L'\0'; +} + +static inline std::list<std::string> loadStrings(const LLSD& sd, const std::string key) +{ + auto toLower = [](std::string& str) { LLStringUtil::toLower(str); }; + return llsd_array_to_list<std::string>(sd[key], toLower); +} + +std::list<std::string> LLEmojiDictionary::loadCategories(const LLSD& sd) +{ + static const std::string categoriesKey("Categories"); + return loadStrings(sd, categoriesKey); +} + +std::list<std::string> LLEmojiDictionary::loadShortCodes(const LLSD& sd) +{ + static const std::string shortCodesKey("ShortCodes"); + return loadStrings(sd, shortCodesKey); } -void LLEmojiDictionary::addEmoji(LLEmojiDescriptor&& descr) +void LLEmojiDictionary::translateCategories(std::list<std::string>& categories) { - mEmojis.push_back(descr); - mEmoji2Descr.insert(std::make_pair(descr.Character, &mEmojis.back())); - for (const std::string& shortCode : descr.ShortCodes) - { - mShortCode2Descr.insert(std::make_pair(shortCode, &mEmojis.back())); - } - for (const std::string& category : descr.Categories) - { - mCategory2Descrs[category].push_back(&mEmojis.back()); - } + for (std::string& category : categories) + { + auto it = mTranslations.find(category); + if (it != mTranslations.end()) + { + category = it->second; + } + } } // ============================================================================ diff --git a/indra/llui/llemojidictionary.h b/indra/llui/llemojidictionary.h index 1507ebfad3..f6442684a7 100644 --- a/indra/llui/llemojidictionary.h +++ b/indra/llui/llemojidictionary.h @@ -36,14 +36,20 @@ struct LLEmojiDescriptor { - LLEmojiDescriptor(const LLSD& descriptor_sd); + llwchar Character; + std::string Category; + std::list<std::string> ShortCodes; + std::string getShortCodes() const; +}; - bool isValid() const; +// ============================================================================ +// LLEmojiGroup class +// - std::string Name; - llwchar Character; - std::list<std::string> ShortCodes; - std::list<std::string> Categories; +struct LLEmojiGroup +{ + llwchar Character; + std::list<std::string> Categories; }; // ============================================================================ @@ -52,36 +58,48 @@ struct LLEmojiDescriptor class LLEmojiDictionary : public LLParamSingleton<LLEmojiDictionary>, public LLInitClass<LLEmojiDictionary> { - LLSINGLETON(LLEmojiDictionary); - ~LLEmojiDictionary() override {}; + LLSINGLETON(LLEmojiDictionary); + ~LLEmojiDictionary() override {}; public: - typedef std::map<llwchar, const LLEmojiDescriptor*> emoji2descr_map_t; - typedef emoji2descr_map_t::value_type emoji2descr_item_t; - typedef std::map<std::string, const LLEmojiDescriptor*> code2descr_map_t; - typedef code2descr_map_t::value_type code2descr_item_t; - typedef std::map<std::string, std::vector<const LLEmojiDescriptor*>> cat2descrs_map_t; - typedef cat2descrs_map_t::value_type cat2descrs_item_t; - - static void initClass(); - LLWString findMatchingEmojis(const std::string& needle) const; - const LLEmojiDescriptor* getDescriptorFromEmoji(llwchar emoji) const; - const LLEmojiDescriptor* getDescriptorFromShortCode(const std::string& short_code) const; - std::string getNameFromEmoji(llwchar ch) const; - bool isEmoji(llwchar ch) const; - - const emoji2descr_map_t& getEmoji2Descr() const { return mEmoji2Descr; } - const code2descr_map_t& getShortCode2Descr() const { return mShortCode2Descr; } - const cat2descrs_map_t& getCategory2Descrs() const { return mCategory2Descrs; } + typedef std::map<std::string, std::string> cat2cat_map_t; + typedef std::map<std::string, const LLEmojiGroup*> cat2group_map_t; + typedef std::map<llwchar, const LLEmojiDescriptor*> emoji2descr_map_t; + typedef std::map<std::string, const LLEmojiDescriptor*> code2descr_map_t; + typedef std::map<std::string, std::vector<const LLEmojiDescriptor*>> cat2descrs_map_t; + + static void initClass(); + LLWString findMatchingEmojis(const std::string& needle) const; + const LLEmojiDescriptor* getDescriptorFromEmoji(llwchar emoji) const; + const LLEmojiDescriptor* getDescriptorFromShortCode(const std::string& short_code) const; + std::string getNameFromEmoji(llwchar ch) const; + bool isEmoji(llwchar ch) const; + + const std::vector<LLEmojiGroup>& getGroups() const { return mGroups; } + const emoji2descr_map_t& getEmoji2Descr() const { return mEmoji2Descr; } + const cat2descrs_map_t& getCategory2Descrs() const { return mCategory2Descrs; } + const code2descr_map_t& getShortCode2Descr() const { return mShortCode2Descr; } private: - void addEmoji(LLEmojiDescriptor&& descr); + void loadTranslations(); + void loadGroups(); + void loadEmojis(); + + static llwchar loadIcon(const LLSD& sd); + static std::list<std::string> loadCategories(const LLSD& sd); + static std::list<std::string> loadShortCodes(const LLSD& sd); + void translateCategories(std::list<std::string>& categories); private: - std::list<LLEmojiDescriptor> mEmojis; - emoji2descr_map_t mEmoji2Descr; - code2descr_map_t mShortCode2Descr; - cat2descrs_map_t mCategory2Descrs; + std::vector<LLEmojiGroup> mGroups; + std::list<LLEmojiDescriptor> mEmojis; + std::list<std::string> mSkipCategories; + + cat2cat_map_t mTranslations; + cat2group_map_t mCategory2Group; + emoji2descr_map_t mEmoji2Descr; + cat2descrs_map_t mCategory2Descrs; + code2descr_map_t mShortCode2Descr; }; // ============================================================================ diff --git a/indra/newview/app_settings/emoji_groups.xml b/indra/newview/app_settings/emoji_groups.xml new file mode 100644 index 0000000000..b433927f91 --- /dev/null +++ b/indra/newview/app_settings/emoji_groups.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" ?> +<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd"> + <array> + <map> + <key>Name</key> + <string>all</string> + <key>Character</key> + <string>🔍</string> + </map> + <map> + <key>Character</key> + <string>😀</string> + <key>Categories</key> + <array> + <string>smileys and emotion</string> + <string>people and body</string> + </array> + </map> + <map> + <key>Character</key> + <string>🥬</string> + <key>Categories</key> + <array> + <string>animals and nature</string> + </array> + </map> + <map> + <key>Character</key> + <string>🍔</string> + <key>Categories</key> + <array> + <string>food and drink</string> + </array> + </map> + <map> + <key>Character</key> + <string>🛩</string> + <key>Categories</key> + <array> + <string>travel and places</string> + </array> + </map> + <map> + <key>Character</key> + <string>🏈</string> + <key>Categories</key> + <array> + <string>activities</string> + </array> + </map> + <map> + <key>Character</key> + <string>💡</string> + <key>Categories</key> + <array> + <string>objects</string> + </array> + </map> + <map> + <key>Character</key> + <string>⚠</string> + <key>Categories</key> + <array> + <string>symbols</string> + </array> + </map> + <map> + <key>Name</key> + <string>others</string> + <key>Character</key> + <string>🌂</string> + </map> + <map> + <key>Name</key> + <string>skip</string> + <key>Categories</key> + <array> + <string>components</string> + </array> + </map> + </array> +</llsd> diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index f63062f03a..9194a49c45 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -27,6 +27,7 @@ #include "llfloateremojipicker.h" +#include "llbutton.h" #include "llcombobox.h" #include "llemojidictionary.h" #include "llfloaterreg.h" @@ -39,179 +40,179 @@ #include "lltextbox.h" #include "llviewerchat.h" -std::string LLFloaterEmojiPicker::mSelectedCategory; -std::string LLFloaterEmojiPicker::mSearchPattern; +size_t LLFloaterEmojiPicker::sSelectedGroupIndex; +std::string LLFloaterEmojiPicker::sSearchPattern; class LLEmojiScrollListItem : public LLScrollListItem { public: - LLEmojiScrollListItem(const llwchar emoji, const LLScrollListItem::Params& params) - : LLScrollListItem(params) - , mEmoji(emoji) - { - } - - llwchar getEmoji() const { return mEmoji; } - - virtual void draw(const LLRect& rect, - const LLColor4& fg_color, - const LLColor4& hover_color, // highlight/hover selection of whole item or cell - const LLColor4& select_color, // highlight/hover selection of whole item or cell - const LLColor4& highlight_color, // highlights contents of cells (ex: text) - S32 column_padding) override - { - LLScrollListItem::draw(rect, fg_color, hover_color, select_color, highlight_color, column_padding); - - LLWString wstr(1, mEmoji); - S32 width = getColumn(0)->getWidth(); - F32 x = rect.mLeft + width / 2; - F32 y = rect.getCenterY(); - LLFontGL::getFontEmoji()->render( - wstr, // wstr - 0, // begin_offset - x, // x - y, // y - LLColor4::white, // color - LLFontGL::HCENTER, // halign - LLFontGL::VCENTER, // valign - LLFontGL::NORMAL, // style - LLFontGL::DROP_SHADOW_SOFT, // shadow - 1, // max_chars - S32_MAX, // max_pixels - nullptr, // right_x - false, // use_ellipses - true); // use_color - } + LLEmojiScrollListItem(const llwchar emoji, const LLScrollListItem::Params& params) + : LLScrollListItem(params) + , mEmoji(emoji) + { + } + + llwchar getEmoji() const { return mEmoji; } + + virtual void draw(const LLRect& rect, + const LLColor4& fg_color, + const LLColor4& hover_color, // highlight/hover selection of whole item or cell + const LLColor4& select_color, // highlight/hover selection of whole item or cell + const LLColor4& highlight_color, // highlights contents of cells (ex: text) + S32 column_padding) override + { + LLScrollListItem::draw(rect, fg_color, hover_color, select_color, highlight_color, column_padding); + + LLWString wstr(1, mEmoji); + S32 width = getColumn(0)->getWidth(); + F32 x = rect.mLeft + width / 2; + F32 y = rect.getCenterY(); + LLFontGL::getFontEmoji()->render( + wstr, // wstr + 0, // begin_offset + x, // x + y, // y + LLColor4::white, // color + LLFontGL::HCENTER, // halign + LLFontGL::VCENTER, // valign + LLFontGL::NORMAL, // style + LLFontGL::DROP_SHADOW_SOFT, // shadow + 1, // max_chars + S32_MAX, // max_pixels + nullptr, // right_x + false, // use_ellipses + true); // use_color + } private: - llwchar mEmoji; + llwchar mEmoji; }; class LLEmojiGridRow : public LLScrollingPanel { public: - LLEmojiGridRow(const LLPanel::Params& panel_params, - const LLScrollingPanelList::Params& list_params) - : LLScrollingPanel(panel_params) - , mList(new LLScrollingPanelList(list_params)) - { - addChild(mList); - } + LLEmojiGridRow(const LLPanel::Params& panel_params, + const LLScrollingPanelList::Params& list_params) + : LLScrollingPanel(panel_params) + , mList(new LLScrollingPanelList(list_params)) + { + addChild(mList); + } - virtual void updatePanel(BOOL allow_modify) override {} + virtual void updatePanel(BOOL allow_modify) override {} public: - LLScrollingPanelList* mList; + LLScrollingPanelList* mList; }; class LLEmojiGridDivider : public LLScrollingPanel { public: - LLEmojiGridDivider(const LLPanel::Params& panel_params, std::string text) - : LLScrollingPanel(panel_params) - , mText(utf8string_to_wstring(text)) - { - } - - virtual void draw() override - { - LLScrollingPanel::draw(); - - F32 x = 4; // padding-left - F32 y = getRect().getHeight() / 2; - LLFontGL::getFontSansSerifBold()->render( - mText, // wstr - 0, // begin_offset - x, // x - y, // y - LLColor4::white, // color - LLFontGL::LEFT, // halign - LLFontGL::VCENTER, // valign - LLFontGL::NORMAL, // style - LLFontGL::DROP_SHADOW_SOFT, // shadow - mText.size(), // max_chars - S32_MAX, // max_pixels - nullptr, // right_x - false, // use_ellipses - true); // use_color - } - - virtual void updatePanel(BOOL allow_modify) override {} + LLEmojiGridDivider(const LLPanel::Params& panel_params, std::string text) + : LLScrollingPanel(panel_params) + , mText(utf8string_to_wstring(text)) + { + } + + virtual void draw() override + { + LLScrollingPanel::draw(); + + F32 x = 4; // padding-left + F32 y = getRect().getHeight() / 2; + LLFontGL::getFontSansSerifBold()->render( + mText, // wstr + 0, // begin_offset + x, // x + y, // y + LLColor4::white, // color + LLFontGL::LEFT, // halign + LLFontGL::VCENTER, // valign + LLFontGL::NORMAL, // style + LLFontGL::DROP_SHADOW_SOFT, // shadow + mText.size(), // max_chars + S32_MAX, // max_pixels + nullptr, // right_x + false, // use_ellipses + true); // use_color + } + + virtual void updatePanel(BOOL allow_modify) override {} private: - const LLWString mText; + const LLWString mText; }; class LLEmojiGridIcon : public LLScrollingPanel { public: - LLEmojiGridIcon(const LLPanel::Params& panel_params, const LLEmojiDescriptor* descr, std::string category) - : LLScrollingPanel(panel_params) - , mEmoji(descr->Character) - , mText(LLWString(1, mEmoji)) - , mDescr(descr->Name) - , mCategory(category) - { - } - - virtual void draw() override - { - LLScrollingPanel::draw(); - - F32 x = getRect().getWidth() / 2; - F32 y = getRect().getHeight() / 2; - LLFontGL::getFontEmoji()->render( - mText, // wstr - 0, // begin_offset - x, // x - y, // y - LLColor4::white, // color - LLFontGL::HCENTER, // halign - LLFontGL::VCENTER, // valign - LLFontGL::NORMAL, // style - LLFontGL::DROP_SHADOW_SOFT, // shadow - 1, // max_chars - S32_MAX, // max_pixels - nullptr, // right_x - false, // use_ellipses - true); // use_color - } - - virtual void updatePanel(BOOL allow_modify) override {} - - llwchar getEmoji() const { return mEmoji; } - LLWString getText() const { return mText; } - std::string getDescr() const { return mDescr; } - std::string getCategory() const { return mCategory; } + LLEmojiGridIcon(const LLPanel::Params& panel_params, const LLEmojiDescriptor* descr, std::string category) + : LLScrollingPanel(panel_params) + , mEmoji(descr->Character) + , mText(LLWString(1, mEmoji)) + , mDescr(descr->getShortCodes()) + , mCategory(category) + { + } + + virtual void draw() override + { + LLScrollingPanel::draw(); + + F32 x = getRect().getWidth() / 2; + F32 y = getRect().getHeight() / 2; + LLFontGL::getFontEmoji()->render( + mText, // wstr + 0, // begin_offset + x, // x + y, // y + LLColor4::white, // color + LLFontGL::HCENTER, // halign + LLFontGL::VCENTER, // valign + LLFontGL::NORMAL, // style + LLFontGL::DROP_SHADOW_SOFT, // shadow + 1, // max_chars + S32_MAX, // max_pixels + nullptr, // right_x + false, // use_ellipses + true); // use_color + } + + virtual void updatePanel(BOOL allow_modify) override {} + + llwchar getEmoji() const { return mEmoji; } + LLWString getText() const { return mText; } + std::string getDescr() const { return mDescr; } + std::string getCategory() const { return mCategory; } private: - const llwchar mEmoji; - const LLWString mText; - const std::string mDescr; - const std::string mCategory; + const llwchar mEmoji; + const LLWString mText; + const std::string mDescr; + const std::string mCategory; }; LLFloaterEmojiPicker* LLFloaterEmojiPicker::getInstance() { - LLFloaterEmojiPicker* floater = LLFloaterReg::getTypedInstance<LLFloaterEmojiPicker>("emoji_picker"); - if (!floater) - LL_ERRS() << "Cannot instantiate emoji picker" << LL_ENDL; - return floater; + LLFloaterEmojiPicker* floater = LLFloaterReg::getTypedInstance<LLFloaterEmojiPicker>("emoji_picker"); + if (!floater) + LL_ERRS() << "Cannot instantiate emoji picker" << LL_ENDL; + return floater; } LLFloaterEmojiPicker* LLFloaterEmojiPicker::showInstance(pick_callback_t pick_callback, close_callback_t close_callback) { - LLFloaterEmojiPicker* floater = getInstance(); - floater->show(pick_callback, close_callback); - return floater; + LLFloaterEmojiPicker* floater = getInstance(); + floater->show(pick_callback, close_callback); + return floater; } void LLFloaterEmojiPicker::show(pick_callback_t pick_callback, close_callback_t close_callback) { - mEmojiPickCallback = pick_callback; - mFloaterCloseCallback = close_callback; - openFloater(mKey); - setFocus(TRUE); + mEmojiPickCallback = pick_callback; + mFloaterCloseCallback = close_callback; + openFloater(mKey); + setFocus(TRUE); } LLFloaterEmojiPicker::LLFloaterEmojiPicker(const LLSD& key) @@ -221,313 +222,415 @@ LLFloaterEmojiPicker::LLFloaterEmojiPicker(const LLSD& key) BOOL LLFloaterEmojiPicker::postBuild() { - // Should be initialized first - mPreviewEmoji = getChild<LLButton>("PreviewEmoji"); - mPreviewEmoji->setClickedCallback([this](LLUICtrl*, const LLSD&) { onPreviewEmojiClick(); }); - - mDescription = getChild<LLTextBox>("Description"); - mDescription->setVisible(FALSE); - - mCategory = getChild<LLComboBox>("Category"); - mCategory->setCommitCallback([this](LLUICtrl*, const LLSD&) { onCategoryCommit(); }); - const LLEmojiDictionary::cat2descrs_map_t& cat2Descrs = LLEmojiDictionary::instance().getCategory2Descrs(); - mCategory->clearRows(); - for (const LLEmojiDictionary::cat2descrs_item_t& item : cat2Descrs) - { - std::string value = item.first; - std::string name = value; - LLStringUtil::capitalize(name); - mCategory->add(name, value); - } - mCategory->setSelectedByValue(mSelectedCategory, true); - - mSearch = getChild<LLLineEditor>("Search"); - mSearch->setKeystrokeCallback([this](LLLineEditor*, void*) { onSearchKeystroke(); }, NULL); - mSearch->setFont(LLViewerChat::getChatFont()); - mSearch->setText(mSearchPattern); - - mEmojiScroll = getChild<LLScrollContainer>("EmojiGridContainer"); - mEmojiScroll->setMouseEnterCallback([this](LLUICtrl*, const LLSD&) { onGridMouseEnter(); }); - mEmojiScroll->setMouseLeaveCallback([this](LLUICtrl*, const LLSD&) { onGridMouseLeave(); }); - - mEmojiGrid = getChild<LLScrollingPanelList>("EmojiGrid"); - - fillEmojiGrid(); - - return TRUE; + // Should be initialized first + mPreviewEmoji = getChild<LLButton>("PreviewEmoji"); + mPreviewEmoji->setClickedCallback([this](LLUICtrl*, const LLSD&) { onPreviewEmojiClick(); }); + + mDescription = getChild<LLTextBox>("Description"); + mDescription->setVisible(FALSE); + + mGroups = getChild<LLPanel>("Groups"); + mBadge = getChild<LLPanel>("Badge"); + + mSearch = getChild<LLLineEditor>("Search"); + mSearch->setKeystrokeCallback([this](LLLineEditor*, void*) { onSearchKeystroke(); }, NULL); + mSearch->setFont(LLViewerChat::getChatFont()); + mSearch->setText(sSearchPattern); + + mEmojiScroll = getChild<LLScrollContainer>("EmojiGridContainer"); + mEmojiScroll->setMouseEnterCallback([this](LLUICtrl*, const LLSD&) { onGridMouseEnter(); }); + mEmojiScroll->setMouseLeaveCallback([this](LLUICtrl*, const LLSD&) { onGridMouseLeave(); }); + + mEmojiGrid = getChild<LLScrollingPanelList>("EmojiGrid"); + + fillGroups(); + fillEmojis(); + + return TRUE; } void LLFloaterEmojiPicker::dirtyRect() { - super::dirtyRect(); + super::dirtyRect(); - if (mEmojiScroll && mEmojiScroll->getRect().getWidth() != mRecentGridWidth) - { - fillEmojiGrid(); - } + if (mEmojiScroll && mEmojiScroll->getRect().getWidth() != mRecentGridWidth) + { + moveGroups(); + fillEmojis(true); + } } LLFloaterEmojiPicker::~LLFloaterEmojiPicker() { - gFocusMgr.releaseFocusIfNeeded( this ); + gFocusMgr.releaseFocusIfNeeded( this ); +} + +void LLFloaterEmojiPicker::fillGroups() +{ + LLButton::Params params; + params.font = LLFontGL::getFontEmoji(); + //params.use_font_color = true; + + LLRect rect; + rect.mTop = mGroups->getRect().getHeight(); + rect.mBottom = mBadge->getRect().getHeight(); + + const std::vector<LLEmojiGroup>& groups = LLEmojiDictionary::instance().getGroups(); + for (const LLEmojiGroup& group : groups) + { + LLButton* button = LLUICtrlFactory::create<LLButton>(params); + button->setClickedCallback([this](LLUICtrl* ctrl, const LLSD&) { onGroupButtonClick(ctrl); }); + button->setMouseEnterCallback([this](LLUICtrl* ctrl, const LLSD&) { onGroupButtonMouseEnter(ctrl); }); + button->setMouseLeaveCallback([this](LLUICtrl* ctrl, const LLSD&) { onGroupButtonMouseLeave(ctrl); }); + + button->setRect(rect); + + LLUIString text; + text.insert(0, LLWString(1, group.Character)); + button->setLabel(text); + + if (mGroupButtons.size() == sSelectedGroupIndex) + { + button->setToggleState(TRUE); + } + + mGroupButtons.push_back(button); + mGroups->addChild(button); + } + + moveGroups(); +} + +void LLFloaterEmojiPicker::moveGroups() +{ + const std::vector<LLEmojiGroup>& groups = LLEmojiDictionary::instance().getGroups(); + if (groups.empty()) + return; + + int badgeWidth = mGroups->getRect().getWidth() / groups.size(); + if (badgeWidth == mRecentBadgeWidth) + return; + + mRecentBadgeWidth = badgeWidth; + + for (int i = 0; i < mGroupButtons.size(); ++i) + { + LLRect rect = mGroupButtons[i]->getRect(); + rect.mLeft = badgeWidth * i; + rect.mRight = rect.mLeft + badgeWidth; + mGroupButtons[i]->setRect(rect); + } + + LLRect rect = mBadge->getRect(); + rect.mLeft = badgeWidth * sSelectedGroupIndex; + rect.mRight = rect.mLeft + badgeWidth; + mBadge->setRect(rect); } -void LLFloaterEmojiPicker::fillEmojiGrid() +void LLFloaterEmojiPicker::fillEmojis(bool fromResize) { - mRecentGridWidth = mEmojiScroll->getRect().getWidth(); - - S32 scrollbarSize = mEmojiScroll->getSize(); - if (scrollbarSize < 0) - { - static LLUICachedControl<S32> scrollbar_size_control("UIScrollbarSize", 0); - scrollbarSize = scrollbar_size_control; - } - - const S32 clientWidth = mRecentGridWidth - scrollbarSize - mEmojiScroll->getBorderWidth() * 2; - const S32 gridPadding = mEmojiGrid->getPadding(); - const S32 iconSpacing = mEmojiGrid->getSpacing(); - const S32 rowWidth = clientWidth - gridPadding * 2; - const S32 iconSize = 28; // icon width and height - const S32 maxIcons = llmax(1, (rowWidth + iconSpacing) / (iconSize + iconSpacing)); - - // Optimization: don't rearrange for different widths with the same maxIcons - if (maxIcons == mRecentMaxIcons) - return; - mRecentMaxIcons = maxIcons; - - mHoveredIcon = nullptr; - mEmojiGrid->clearPanels(); - mPreviewEmoji->setLabel(LLUIString()); - - if (mEmojiGrid->getRect().getWidth() != clientWidth) - { - LLRect rect = mEmojiGrid->getRect(); - rect.mRight = rect.mLeft + clientWidth; - mEmojiGrid->setRect(rect); - } - - LLPanel::Params row_panel_params; - row_panel_params.rect = LLRect(0, iconSize, rowWidth, 0); - - LLScrollingPanelList::Params row_list_params; - row_list_params.rect = row_panel_params.rect; - row_list_params.is_horizontal = TRUE; - row_list_params.padding = 0; - row_list_params.spacing = iconSpacing; - - LLPanel::Params icon_params; - LLRect icon_rect(0, iconSize, iconSize, 0); - - static const LLColor4 bgcolors[] = - { - LLColor4(0.8f, 0.6f, 0.8f, 1.0f), - LLColor4(0.8f, 0.8f, 0.4f, 1.0f), - LLColor4(0.6f, 0.6f, 0.8f, 1.0f), - LLColor4(0.4f, 0.7f, 0.4f, 1.0f), - LLColor4(0.5f, 0.7f, 0.9f, 1.0f), - LLColor4(0.7f, 0.8f, 0.2f, 1.0f) - }; - - static constexpr U32 bgcolorCount = sizeof(bgcolors) / sizeof(*bgcolors); - - auto listCategory = [&](std::string category, const std::vector<const LLEmojiDescriptor*>& emojis, bool showDivider) - { - int iconIndex = 0; - LLEmojiGridRow* row = nullptr; - LLStringUtil::capitalize(category); - for (const LLEmojiDescriptor* descr : emojis) - { - if (mSearchPattern.empty() || matchesPattern(descr)) - { - // Place a category title if needed - if (showDivider) - { - LLEmojiGridDivider* div = new LLEmojiGridDivider(row_panel_params, category); - mEmojiGrid->addPanel(div, true); - showDivider = false; - } - - // Place a new row each (maxIcons) icons - if (!(iconIndex % maxIcons)) - { - row = new LLEmojiGridRow(row_panel_params, row_list_params); - mEmojiGrid->addPanel(row, true); - } - - // Place a new icon to the current row - LLEmojiGridIcon* icon = new LLEmojiGridIcon(icon_params, descr, category); - icon->setMouseEnterCallback([this](LLUICtrl* ctrl, const LLSD&) { onEmojiMouseEnter(ctrl); }); - icon->setMouseLeaveCallback([this](LLUICtrl* ctrl, const LLSD&) { onEmojiMouseLeave(ctrl); }); - icon->setMouseUpCallback([this](LLUICtrl* ctrl, S32, S32, MASK mask) { onEmojiMouseClick(ctrl, mask); }); - icon->setBackgroundColor(bgcolors[iconIndex % bgcolorCount]); - icon->setBackgroundOpaque(1); - icon->setRect(icon_rect); - row->mList->addPanel(icon, true); - - iconIndex++; - } - } - }; - - const LLEmojiDictionary::cat2descrs_map_t& category2Descr = LLEmojiDictionary::instance().getCategory2Descrs(); - if (mSelectedCategory.empty()) - { - // List all categories with titles - for (const LLEmojiDictionary::cat2descrs_item_t& item : category2Descr) - { - listCategory(item.first, item.second, TRUE); - } - } - else - { - // List one category without title - const LLEmojiDictionary::cat2descrs_map_t::const_iterator& item = category2Descr.find(mSelectedCategory); - if (item != category2Descr.end()) - { - listCategory(mSelectedCategory, item->second, FALSE); - } - } + mRecentGridWidth = mEmojiScroll->getRect().getWidth(); + + S32 scrollbarSize = mEmojiScroll->getSize(); + if (scrollbarSize < 0) + { + static LLUICachedControl<S32> scrollbar_size_control("UIScrollbarSize", 0); + scrollbarSize = scrollbar_size_control; + } + + const S32 clientWidth = mRecentGridWidth - scrollbarSize - mEmojiScroll->getBorderWidth() * 2; + const S32 gridPadding = mEmojiGrid->getPadding(); + const S32 iconSpacing = mEmojiGrid->getSpacing(); + const S32 rowWidth = clientWidth - gridPadding * 2; + const S32 iconSize = 28; // icon width and height + const S32 maxIcons = llmax(1, (rowWidth + iconSpacing) / (iconSize + iconSpacing)); + + // Optimization: don't rearrange for different widths with the same maxIcons + if (fromResize && (maxIcons == mRecentMaxIcons)) + return; + + mRecentMaxIcons = maxIcons; + + mHoveredIcon = nullptr; + mEmojiGrid->clearPanels(); + mPreviewEmoji->setLabel(LLUIString()); + + if (mEmojiGrid->getRect().getWidth() != clientWidth) + { + LLRect rect = mEmojiGrid->getRect(); + rect.mRight = rect.mLeft + clientWidth; + mEmojiGrid->setRect(rect); + } + + LLPanel::Params row_panel_params; + row_panel_params.rect = LLRect(0, iconSize, rowWidth, 0); + + LLScrollingPanelList::Params row_list_params; + row_list_params.rect = row_panel_params.rect; + row_list_params.is_horizontal = TRUE; + row_list_params.padding = 0; + row_list_params.spacing = iconSpacing; + + LLPanel::Params icon_params; + LLRect icon_rect(0, iconSize, iconSize, 0); + + static const LLColor4 bgcolors[] = + { + LLColor4(0.8f, 0.6f, 0.8f, 1.0f), + LLColor4(0.8f, 0.8f, 0.4f, 1.0f), + LLColor4(0.6f, 0.6f, 0.8f, 1.0f), + LLColor4(0.4f, 0.7f, 0.4f, 1.0f), + LLColor4(0.5f, 0.7f, 0.9f, 1.0f), + LLColor4(0.7f, 0.8f, 0.2f, 1.0f) + }; + + static constexpr U32 bgcolorCount = sizeof(bgcolors) / sizeof(*bgcolors); + + auto listCategory = [&](std::string category, const std::vector<const LLEmojiDescriptor*>& emojis) + { + int iconIndex = 0; + bool showDivider = true; + LLEmojiGridRow* row = nullptr; + LLStringUtil::capitalize(category); + for (const LLEmojiDescriptor* descr : emojis) + { + if (sSearchPattern.empty() || matchesPattern(descr)) + { + // Place a category title if needed + if (showDivider) + { + LLEmojiGridDivider* div = new LLEmojiGridDivider(row_panel_params, category); + mEmojiGrid->addPanel(div, true); + showDivider = false; + } + + // Place a new row each (maxIcons) icons + if (!(iconIndex % maxIcons)) + { + row = new LLEmojiGridRow(row_panel_params, row_list_params); + mEmojiGrid->addPanel(row, true); + } + + // Place a new icon to the current row + LLEmojiGridIcon* icon = new LLEmojiGridIcon(icon_params, descr, category); + icon->setMouseEnterCallback([this](LLUICtrl* ctrl, const LLSD&) { onEmojiMouseEnter(ctrl); }); + icon->setMouseLeaveCallback([this](LLUICtrl* ctrl, const LLSD&) { onEmojiMouseLeave(ctrl); }); + icon->setMouseUpCallback([this](LLUICtrl* ctrl, S32, S32, MASK mask) { onEmojiMouseClick(ctrl, mask); }); + icon->setBackgroundColor(bgcolors[iconIndex % bgcolorCount]); + icon->setBackgroundOpaque(1); + icon->setRect(icon_rect); + row->mList->addPanel(icon, true); + + iconIndex++; + } + } + }; + + const std::vector<LLEmojiGroup>& groups = LLEmojiDictionary::instance().getGroups(); + const LLEmojiDictionary::cat2descrs_map_t& category2Descr = LLEmojiDictionary::instance().getCategory2Descrs(); + if (!sSelectedGroupIndex) + { + // List all groups + for (const LLEmojiGroup& group : groups) + { + // List all categories in group + for (const std::string& category : group.Categories) + { + // List all emojis in category + const LLEmojiDictionary::cat2descrs_map_t::const_iterator& item = category2Descr.find(category); + if (item != category2Descr.end()) + { + listCategory(category, item->second); + } + } + } + } + else + { + // List all categories in the selected group + for (const std::string& category : groups[sSelectedGroupIndex].Categories) + { + // List all emojis in category + const LLEmojiDictionary::cat2descrs_map_t::const_iterator& item = category2Descr.find(category); + if (item != category2Descr.end()) + { + listCategory(category, item->second); + } + } + } } bool LLFloaterEmojiPicker::matchesPattern(const LLEmojiDescriptor* descr) { - if (descr->Name.find(mSearchPattern) != std::string::npos) - return true; - for (const std::string& shortCode : descr->ShortCodes) - if (shortCode.find(mSearchPattern) != std::string::npos) - return true; - return false; + for (const std::string& shortCode : descr->ShortCodes) + if (shortCode.find(sSearchPattern) != std::string::npos) + return true; + return false; } -void LLFloaterEmojiPicker::onCategoryCommit() +void LLFloaterEmojiPicker::onGroupButtonClick(LLUICtrl* ctrl) { - mSelectedCategory = mCategory->getSelectedValue().asString(); - mRecentMaxIcons = 0; - fillEmojiGrid(); + if (LLButton* button = dynamic_cast<LLButton*>(ctrl)) + { + if (button == mGroupButtons[sSelectedGroupIndex] || button->getToggleState()) + return; + + auto it = std::find(mGroupButtons.begin(), mGroupButtons.end(), button); + if (it == mGroupButtons.end()) + return; + + mGroupButtons[sSelectedGroupIndex]->setToggleState(FALSE); + sSelectedGroupIndex = it - mGroupButtons.begin(); + mGroupButtons[sSelectedGroupIndex]->setToggleState(TRUE); + + LLRect rect = mBadge->getRect(); + rect.mLeft = button->getRect().mLeft; + rect.mRight = button->getRect().mRight; + mBadge->setRect(rect); + + mSearch->setFocus(TRUE); + + fillEmojis(); + } } void LLFloaterEmojiPicker::onSearchKeystroke() { - mSearchPattern = mSearch->getText(); - mRecentMaxIcons = 0; - fillEmojiGrid(); + sSearchPattern = mSearch->getText(); + fillEmojis(); } void LLFloaterEmojiPicker::onPreviewEmojiClick() { - if (mEmojiPickCallback) - { - if (LLEmojiGridIcon* icon = dynamic_cast<LLEmojiGridIcon*>(mHoveredIcon)) - { - mEmojiPickCallback(icon->getEmoji()); - } - } + if (mEmojiPickCallback) + { + if (LLEmojiGridIcon* icon = dynamic_cast<LLEmojiGridIcon*>(mHoveredIcon)) + { + mEmojiPickCallback(icon->getEmoji()); + } + } } void LLFloaterEmojiPicker::onGridMouseEnter() { - mSearch->setVisible(FALSE); - mDescription->setText(LLStringExplicit(""), LLStyle::Params()); - mDescription->setVisible(TRUE); + mSearch->setVisible(FALSE); + mDescription->setText(LLStringExplicit(""), LLStyle::Params()); + mDescription->setVisible(TRUE); } void LLFloaterEmojiPicker::onGridMouseLeave() { - mDescription->setVisible(FALSE); - mDescription->setText(LLStringExplicit(""), LLStyle::Params()); - mSearch->setVisible(TRUE); - mSearch->setFocus(TRUE); + mDescription->setVisible(FALSE); + mDescription->setText(LLStringExplicit(""), LLStyle::Params()); + mSearch->setVisible(TRUE); + mSearch->setFocus(TRUE); +} + +void LLFloaterEmojiPicker::onGroupButtonMouseEnter(LLUICtrl* ctrl) +{ + if (LLButton* button = dynamic_cast<LLButton*>(ctrl)) + { + button->setUseFontColor(TRUE); + } +} + +void LLFloaterEmojiPicker::onGroupButtonMouseLeave(LLUICtrl* ctrl) +{ + if (LLButton* button = dynamic_cast<LLButton*>(ctrl)) + { + button->setUseFontColor(FALSE); + } } void LLFloaterEmojiPicker::onEmojiMouseEnter(LLUICtrl* ctrl) { - if (ctrl) - { - if (mHoveredIcon && mHoveredIcon != ctrl) - { - unselectGridIcon(mHoveredIcon); - } + if (ctrl) + { + if (mHoveredIcon && mHoveredIcon != ctrl) + { + unselectGridIcon(mHoveredIcon); + } - selectGridIcon(ctrl); + selectGridIcon(ctrl); - mHoveredIcon = ctrl; - } + mHoveredIcon = ctrl; + } } void LLFloaterEmojiPicker::onEmojiMouseLeave(LLUICtrl* ctrl) { - if (ctrl) - { - if (ctrl == mHoveredIcon) - { - unselectGridIcon(ctrl); - } - } + if (ctrl) + { + if (ctrl == mHoveredIcon) + { + unselectGridIcon(ctrl); + } + } } void LLFloaterEmojiPicker::onEmojiMouseClick(LLUICtrl* ctrl, MASK mask) { - if (mEmojiPickCallback) - { - if (LLEmojiGridIcon* icon = dynamic_cast<LLEmojiGridIcon*>(ctrl)) - { - mPreviewEmoji->handleAnyMouseClick(0, 0, 0, EMouseClickType::CLICK_LEFT, TRUE); - mPreviewEmoji->handleAnyMouseClick(0, 0, 0, EMouseClickType::CLICK_LEFT, FALSE); - if (!(mask & 4)) - { - closeFloater(); - } - } - } + if (mEmojiPickCallback) + { + if (LLEmojiGridIcon* icon = dynamic_cast<LLEmojiGridIcon*>(ctrl)) + { + mPreviewEmoji->handleAnyMouseClick(0, 0, 0, EMouseClickType::CLICK_LEFT, TRUE); + mPreviewEmoji->handleAnyMouseClick(0, 0, 0, EMouseClickType::CLICK_LEFT, FALSE); + if (!(mask & 4)) + { + closeFloater(); + } + } + } } void LLFloaterEmojiPicker::selectGridIcon(LLUICtrl* ctrl) { - if (LLEmojiGridIcon* icon = dynamic_cast<LLEmojiGridIcon*>(ctrl)) - { - icon->setBackgroundVisible(TRUE); + if (LLEmojiGridIcon* icon = dynamic_cast<LLEmojiGridIcon*>(ctrl)) + { + icon->setBackgroundVisible(TRUE); - LLUIString text; - text.insert(0, icon->getText()); - mPreviewEmoji->setLabel(text); + LLUIString text; + text.insert(0, icon->getText()); + mPreviewEmoji->setLabel(text); - std::string descr = icon->getDescr() + "\n" + icon->getCategory(); - mDescription->setText(LLStringExplicit(descr), LLStyle::Params()); - } + std::string descr = icon->getDescr() + "\n" + icon->getCategory(); + mDescription->setText(LLStringExplicit(descr), LLStyle::Params()); + } } void LLFloaterEmojiPicker::unselectGridIcon(LLUICtrl* ctrl) { - if (LLEmojiGridIcon* icon = dynamic_cast<LLEmojiGridIcon*>(ctrl)) - { - icon->setBackgroundVisible(FALSE); - mPreviewEmoji->setLabel(LLUIString()); - mDescription->setText(LLStringExplicit(""), LLStyle::Params()); - } + if (LLEmojiGridIcon* icon = dynamic_cast<LLEmojiGridIcon*>(ctrl)) + { + icon->setBackgroundVisible(FALSE); + mPreviewEmoji->setLabel(LLUIString()); + mDescription->setText(LLStringExplicit(""), LLStyle::Params()); + } } // virtual BOOL LLFloaterEmojiPicker::handleKeyHere(KEY key, MASK mask) { - if (mask == MASK_NONE) - { - switch (key) - { - case KEY_ESCAPE: - closeFloater(); - return TRUE; - } - } - - return LLFloater::handleKeyHere(key, mask); + if (mask == MASK_NONE) + { + switch (key) + { + case KEY_ESCAPE: + closeFloater(); + return TRUE; + } + } + + return LLFloater::handleKeyHere(key, mask); } // virtual void LLFloaterEmojiPicker::closeFloater(bool app_quitting) { - LLFloater::closeFloater(app_quitting); - if (mFloaterCloseCallback) - { - mFloaterCloseCallback(); - } + LLFloater::closeFloater(app_quitting); + if (mFloaterCloseCallback) + { + mFloaterCloseCallback(); + } } diff --git a/indra/newview/llfloateremojipicker.h b/indra/newview/llfloateremojipicker.h index 300d9a4d4a..7fa6ea46b0 100644 --- a/indra/newview/llfloateremojipicker.h +++ b/indra/newview/llfloateremojipicker.h @@ -33,62 +33,70 @@ struct LLEmojiDescriptor; class LLFloaterEmojiPicker : public LLFloater { - using super = LLFloater; + using super = LLFloater; public: - // The callback function will be called with an emoji char. - typedef boost::function<void (llwchar)> pick_callback_t; - typedef boost::function<void ()> close_callback_t; + // The callback function will be called with an emoji char. + typedef boost::function<void (llwchar)> pick_callback_t; + typedef boost::function<void ()> close_callback_t; - // Call this to select an emoji. - static LLFloaterEmojiPicker* getInstance(); - static LLFloaterEmojiPicker* showInstance(pick_callback_t pick_callback = nullptr, close_callback_t close_callback = nullptr); + // Call this to select an emoji. + static LLFloaterEmojiPicker* getInstance(); + static LLFloaterEmojiPicker* showInstance(pick_callback_t pick_callback = nullptr, close_callback_t close_callback = nullptr); - LLFloaterEmojiPicker(const LLSD& key); - virtual ~LLFloaterEmojiPicker(); + LLFloaterEmojiPicker(const LLSD& key); + virtual ~LLFloaterEmojiPicker(); - virtual BOOL postBuild() override; - virtual void dirtyRect() override; + virtual BOOL postBuild() override; + virtual void dirtyRect() override; - void show(pick_callback_t pick_callback = nullptr, close_callback_t close_callback = nullptr); + void show(pick_callback_t pick_callback = nullptr, close_callback_t close_callback = nullptr); - virtual void closeFloater(bool app_quitting = false) override; + virtual void closeFloater(bool app_quitting = false) override; private: - void fillEmojiGrid(); - - bool matchesPattern(const LLEmojiDescriptor* descr); - - void onCategoryCommit(); - void onSearchKeystroke(); - void onPreviewEmojiClick(); - void onGridMouseEnter(); - void onGridMouseLeave(); - void onEmojiMouseEnter(LLUICtrl* ctrl); - void onEmojiMouseLeave(LLUICtrl* ctrl); - void onEmojiMouseClick(LLUICtrl* ctrl, MASK mask); - - void selectGridIcon(LLUICtrl* ctrl); - void unselectGridIcon(LLUICtrl* ctrl); - - virtual BOOL handleKeyHere(KEY key, MASK mask) override; - - class LLComboBox* mCategory { nullptr }; - class LLLineEditor* mSearch { nullptr }; - class LLScrollContainer* mEmojiScroll { nullptr }; - class LLScrollingPanelList* mEmojiGrid { nullptr }; - class LLButton* mPreviewEmoji { nullptr }; - class LLTextBox* mDescription { nullptr }; - - pick_callback_t mEmojiPickCallback; - close_callback_t mFloaterCloseCallback; - - S32 mRecentGridWidth { 0 }; - S32 mRecentMaxIcons { 0 }; - LLUICtrl* mHoveredIcon { nullptr }; - - static std::string mSelectedCategory; - static std::string mSearchPattern; + void fillGroups(); + void moveGroups(); + void fillEmojis(bool fromResize = false); + + bool matchesPattern(const LLEmojiDescriptor* descr); + + void onGroupButtonClick(LLUICtrl* ctrl); + void onSearchKeystroke(); + void onPreviewEmojiClick(); + void onGridMouseEnter(); + void onGridMouseLeave(); + void onGroupButtonMouseEnter(LLUICtrl* ctrl); + void onGroupButtonMouseLeave(LLUICtrl* ctrl); + void onEmojiMouseEnter(LLUICtrl* ctrl); + void onEmojiMouseLeave(LLUICtrl* ctrl); + void onEmojiMouseClick(LLUICtrl* ctrl, MASK mask); + + void selectGridIcon(LLUICtrl* ctrl); + void unselectGridIcon(LLUICtrl* ctrl); + + virtual BOOL handleKeyHere(KEY key, MASK mask) override; + + class LLPanel* mGroups { nullptr }; + class LLPanel* mBadge { nullptr }; + class LLLineEditor* mSearch { nullptr }; + class LLScrollContainer* mEmojiScroll { nullptr }; + class LLScrollingPanelList* mEmojiGrid { nullptr }; + class LLButton* mPreviewEmoji { nullptr }; + class LLTextBox* mDescription { nullptr }; + + pick_callback_t mEmojiPickCallback; + close_callback_t mFloaterCloseCallback; + + std::vector<class LLButton*> mGroupButtons; + + S32 mRecentBadgeWidth { 0 }; + S32 mRecentGridWidth { 0 }; + S32 mRecentMaxIcons { 0 }; + LLUICtrl* mHoveredIcon { nullptr }; + + static size_t sSelectedGroupIndex; + static std::string sSearchPattern; }; #endif diff --git a/indra/newview/skins/default/xui/da/emoji_categories.xml b/indra/newview/skins/default/xui/da/emoji_categories.xml new file mode 100644 index 0000000000..456b18e4e2 --- /dev/null +++ b/indra/newview/skins/default/xui/da/emoji_categories.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" ?> +<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd"> + <array> + <map> + <key>Name</key> + <string>smileys and emotion</string> + <key>Category</key> + <string>smileys and følelser</string> + </map> + <map> + <key>Name</key> + <string>people and body</string> + <key>Category</key> + <string>mennesker and krop</string> + </map> + <map> + <key>Name</key> + <string>components</string> + <key>Category</key> + <string>komponenter</string> + </map> + <map> + <key>Name</key> + <string>animals and nature</string> + <key>Category</key> + <string>dyr and natur</string> + </map> + <map> + <key>Name</key> + <string>food and drink</string> + <key>Category</key> + <string>mad and drikke</string> + </map> + <map> + <key>Name</key> + <string>travel and places</string> + <key>Category</key> + <string>rejser and steder</string> + </map> + <map> + <key>Name</key> + <string>activities</string> + <key>Category</key> + <string>oplevelser</string> + </map> + <map> + <key>Name</key> + <string>objects</string> + <key>Category</key> + <string>objekter</string> + </map> + <map> + <key>Name</key> + <string>symbols</string> + <key>Category</key> + <string>symboler</string> + </map> + </array> +</llsd> diff --git a/indra/newview/skins/default/xui/de/emoji_categories.xml b/indra/newview/skins/default/xui/de/emoji_categories.xml new file mode 100644 index 0000000000..ed63d0bac9 --- /dev/null +++ b/indra/newview/skins/default/xui/de/emoji_categories.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" ?> +<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd"> + <array> + <map> + <key>Name</key> + <string>smileys and emotion</string> + <key>Category</key> + <string>Smileys and Emotionen</string> + </map> + <map> + <key>Name</key> + <string>people and body</string> + <key>Category</key> + <string>Menschen and Körper</string> + </map> + <map> + <key>Name</key> + <string>components</string> + <key>Category</key> + <string>Komponenten</string> + </map> + <map> + <key>Name</key> + <string>animals and nature</string> + <key>Category</key> + <string>Tiere and Natur</string> + </map> + <map> + <key>Name</key> + <string>food and drink</string> + <key>Category</key> + <string>Essen and Trinken</string> + </map> + <map> + <key>Name</key> + <string>travel and places</string> + <key>Category</key> + <string>Reisen and Orte</string> + </map> + <map> + <key>Name</key> + <string>activities</string> + <key>Category</key> + <string>Aktivitäten</string> + </map> + <map> + <key>Name</key> + <string>objects</string> + <key>Category</key> + <string>Gegenstände</string> + </map> + <map> + <key>Name</key> + <string>symbols</string> + <key>Category</key> + <string>Symbole</string> + </map> + </array> +</llsd> diff --git a/indra/newview/skins/default/xui/en/emoji_categories.xml b/indra/newview/skins/default/xui/en/emoji_categories.xml new file mode 100644 index 0000000000..0315d0c43a --- /dev/null +++ b/indra/newview/skins/default/xui/en/emoji_categories.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" ?> +<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd"> + <array> + <map> + <key>Name</key> + <string>smileys and emotion</string> + <key>Category</key> + <string>smileys and emotion</string> + </map> + <map> + <key>Name</key> + <string>people and body</string> + <key>Category</key> + <string>people and body</string> + </map> + <map> + <key>Name</key> + <string>components</string> + <key>Category</key> + <string>components</string> + </map> + <map> + <key>Name</key> + <string>animals and nature</string> + <key>Category</key> + <string>animals and nature</string> + </map> + <map> + <key>Name</key> + <string>food and drink</string> + <key>Category</key> + <string>food and drink</string> + </map> + <map> + <key>Name</key> + <string>travel and places</string> + <key>Category</key> + <string>travel and places</string> + </map> + <map> + <key>Name</key> + <string>activities</string> + <key>Category</key> + <string>activities</string> + </map> + <map> + <key>Name</key> + <string>objects</string> + <key>Category</key> + <string>objects</string> + </map> + <map> + <key>Name</key> + <string>symbols</string> + <key>Category</key> + <string>symbols</string> + </map> + </array> +</llsd> diff --git a/indra/newview/skins/default/xui/en/floater_emoji_picker.xml b/indra/newview/skins/default/xui/en/floater_emoji_picker.xml index 2e153933c0..2a534b23b9 100644 --- a/indra/newview/skins/default/xui/en/floater_emoji_picker.xml +++ b/indra/newview/skins/default/xui/en/floater_emoji_picker.xml @@ -7,8 +7,9 @@ legacy_header_height="0" can_resize="true" layout="topleft" + min_width="250" height="400" - width="200"> + width="250"> <line_editor name="Search" label="Type to search" @@ -19,7 +20,7 @@ bottom="14" left="34" height="29" - width="162" /> + width="212" /> <text name="Description" layout="bottomleft" @@ -28,7 +29,7 @@ bottom="14" left="42" height="29" - width="150" /> + width="200" /> <button name="PreviewEmoji" layout="bottomleft" @@ -46,7 +47,7 @@ top="25" left="0" height="330" - width="200"> + width="250"> <scrolling_panel_list name="EmojiGrid" layout="topleft" @@ -55,16 +56,26 @@ spacing="0" top="0" left="0" - width="200"/> + width="250"/> </scroll_container> - <combo_box - name="Category" - label="Choose a category" + <panel + name="Groups" layout="topleft" follows="top|left|right" - allow_text_entry="true" top="0" - left="2" + left="0" height="25" - width="196" /> + width="250"> + <panel + name="Badge" + layout="bottomleft" + follows="bottom|left" + background_visible="true" + background_opaque="true" + bg_opaque_color="FrogGreen" + bottom="0" + height="2" + width="20" + /> + </panel> </floater> diff --git a/indra/newview/skins/default/xui/es/emoji_categories.xml b/indra/newview/skins/default/xui/es/emoji_categories.xml new file mode 100644 index 0000000000..b1b73eba5e --- /dev/null +++ b/indra/newview/skins/default/xui/es/emoji_categories.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" ?> +<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd"> + <array> + <map> + <key>Name</key> + <string>smileys and emotion</string> + <key>Category</key> + <string>emoticonos y emoción</string> + </map> + <map> + <key>Name</key> + <string>people and body</string> + <key>Category</key> + <string>personas y cuerpo</string> + </map> + <map> + <key>Name</key> + <string>components</string> + <key>Category</key> + <string>componentes</string> + </map> + <map> + <key>Name</key> + <string>animals and nature</string> + <key>Category</key> + <string>animales y la naturaleza</string> + </map> + <map> + <key>Name</key> + <string>food and drink</string> + <key>Category</key> + <string>comida y bebida</string> + </map> + <map> + <key>Name</key> + <string>travel and places</string> + <key>Category</key> + <string>viajes y lugares</string> + </map> + <map> + <key>Name</key> + <string>activities</string> + <key>Category</key> + <string>actividades</string> + </map> + <map> + <key>Name</key> + <string>objects</string> + <key>Category</key> + <string>objetos</string> + </map> + <map> + <key>Name</key> + <string>symbols</string> + <key>Category</key> + <string>símbolos</string> + </map> + </array> +</llsd> diff --git a/indra/newview/skins/default/xui/fr/emoji_categories.xml b/indra/newview/skins/default/xui/fr/emoji_categories.xml new file mode 100644 index 0000000000..38dc9cb8f8 --- /dev/null +++ b/indra/newview/skins/default/xui/fr/emoji_categories.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" ?> +<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd"> + <array> + <map> + <key>Name</key> + <string>smileys and emotion</string> + <key>Category</key> + <string>smileys et émotion</string> + </map> + <map> + <key>Name</key> + <string>people and body</string> + <key>Category</key> + <string>les gens et le corps</string> + </map> + <map> + <key>Name</key> + <string>components</string> + <key>Category</key> + <string>composants</string> + </map> + <map> + <key>Name</key> + <string>animals and nature</string> + <key>Category</key> + <string>animaux et la nature</string> + </map> + <map> + <key>Name</key> + <string>food and drink</string> + <key>Category</key> + <string>nourriture et boissons</string> + </map> + <map> + <key>Name</key> + <string>travel and places</string> + <key>Category</key> + <string>voyages et lieux</string> + </map> + <map> + <key>Name</key> + <string>activities</string> + <key>Category</key> + <string>activités</string> + </map> + <map> + <key>Name</key> + <string>objects</string> + <key>Category</key> + <string>objets</string> + </map> + <map> + <key>Name</key> + <string>symbols</string> + <key>Category</key> + <string>symboles</string> + </map> + </array> +</llsd> diff --git a/indra/newview/skins/default/xui/it/emoji_categories.xml b/indra/newview/skins/default/xui/it/emoji_categories.xml new file mode 100644 index 0000000000..a4782e60a6 --- /dev/null +++ b/indra/newview/skins/default/xui/it/emoji_categories.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" ?> +<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd"> + <array> + <map> + <key>Name</key> + <string>smileys and emotion</string> + <key>Category</key> + <string>smileys and emozione</string> + </map> + <map> + <key>Name</key> + <string>people and body</string> + <key>Category</key> + <string>persone e corpo</string> + </map> + <map> + <key>Name</key> + <string>components</string> + <key>Category</key> + <string>componenti</string> + </map> + <map> + <key>Name</key> + <string>animals and nature</string> + <key>Category</key> + <string>animali and natura</string> + </map> + <map> + <key>Name</key> + <string>food and drink</string> + <key>Category</key> + <string>cibo e bevande</string> + </map> + <map> + <key>Name</key> + <string>travel and places</string> + <key>Category</key> + <string>viaggi and luoghi</string> + </map> + <map> + <key>Name</key> + <string>activities</string> + <key>Category</key> + <string>attività</string> + </map> + <map> + <key>Name</key> + <string>objects</string> + <key>Category</key> + <string>oggetti</string> + </map> + <map> + <key>Name</key> + <string>symbols</string> + <key>Category</key> + <string>simboli</string> + </map> + </array> +</llsd> diff --git a/indra/newview/skins/default/xui/ja/emoji_categories.xml b/indra/newview/skins/default/xui/ja/emoji_categories.xml new file mode 100644 index 0000000000..7750f4ad2e --- /dev/null +++ b/indra/newview/skins/default/xui/ja/emoji_categories.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" ?> +<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd"> + <array> + <map> + <key>Name</key> + <string>smileys and emotion</string> + <key>Category</key> + <string>スマイリーと感情</string> + </map> + <map> + <key>Name</key> + <string>people and body</string> + <key>Category</key> + <string>人体</string> + </map> + <map> + <key>Name</key> + <string>components</string> + <key>Category</key> + <string>コンポーネント</string> + </map> + <map> + <key>Name</key> + <string>animals and nature</string> + <key>Category</key> + <string>動物自然</string> + </map> + <map> + <key>Name</key> + <string>food and drink</string> + <key>Category</key> + <string>飲み物・食べ物</string> + </map> + <map> + <key>Name</key> + <string>travel and places</string> + <key>Category</key> + <string>旅行・場所</string> + </map> + <map> + <key>Name</key> + <string>activities</string> + <key>Category</key> + <string>有効化</string> + </map> + <map> + <key>Name</key> + <string>objects</string> + <key>Category</key> + <string>オブジェクト</string> + </map> + <map> + <key>Name</key> + <string>symbols</string> + <key>Category</key> + <string>シンボル</string> + </map> + </array> +</llsd> diff --git a/indra/newview/skins/default/xui/pl/emoji_categories.xml b/indra/newview/skins/default/xui/pl/emoji_categories.xml new file mode 100644 index 0000000000..9aad7af794 --- /dev/null +++ b/indra/newview/skins/default/xui/pl/emoji_categories.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" ?> +<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd"> + <array> + <map> + <key>Name</key> + <string>smileys and emotion</string> + <key>Category</key> + <string>buźki and emocje</string> + </map> + <map> + <key>Name</key> + <string>people and body</string> + <key>Category</key> + <string>ludzie and ciało</string> + </map> + <map> + <key>Name</key> + <string>components</string> + <key>Category</key> + <string>składniki</string> + </map> + <map> + <key>Name</key> + <string>animals and nature</string> + <key>Category</key> + <string>zwierzęta and przyroda</string> + </map> + <map> + <key>Name</key> + <string>food and drink</string> + <key>Category</key> + <string>jedzenie i picie</string> + </map> + <map> + <key>Name</key> + <string>travel and places</string> + <key>Category</key> + <string>podróże and miejsca</string> + </map> + <map> + <key>Name</key> + <string>activities</string> + <key>Category</key> + <string>aktywność</string> + </map> + <map> + <key>Name</key> + <string>objects</string> + <key>Category</key> + <string>objekt</string> + </map> + <map> + <key>Name</key> + <string>symbols</string> + <key>Category</key> + <string>symbole</string> + </map> + </array> +</llsd> diff --git a/indra/newview/skins/default/xui/pt/emoji_categories.xml b/indra/newview/skins/default/xui/pt/emoji_categories.xml new file mode 100644 index 0000000000..887444b957 --- /dev/null +++ b/indra/newview/skins/default/xui/pt/emoji_categories.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" ?> +<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd"> + <array> + <map> + <key>Name</key> + <string>smileys and emotion</string> + <key>Category</key> + <string>sorrisos e emoção</string> + </map> + <map> + <key>Name</key> + <string>people and body</string> + <key>Category</key> + <string>pessoas e corpo</string> + </map> + <map> + <key>Name</key> + <string>components</string> + <key>Category</key> + <string>componentes</string> + </map> + <map> + <key>Name</key> + <string>animals and nature</string> + <key>Category</key> + <string>animais e natureza</string> + </map> + <map> + <key>Name</key> + <string>food and drink</string> + <key>Category</key> + <string>comida e bebida</string> + </map> + <map> + <key>Name</key> + <string>travel and places</string> + <key>Category</key> + <string>viagens e lugares</string> + </map> + <map> + <key>Name</key> + <string>activities</string> + <key>Category</key> + <string>atividades</string> + </map> + <map> + <key>Name</key> + <string>objects</string> + <key>Category</key> + <string>objetos</string> + </map> + <map> + <key>Name</key> + <string>symbols</string> + <key>Category</key> + <string>símbolos</string> + </map> + </array> +</llsd> diff --git a/indra/newview/skins/default/xui/ru/emoji_categories.xml b/indra/newview/skins/default/xui/ru/emoji_categories.xml new file mode 100644 index 0000000000..b08f0d8117 --- /dev/null +++ b/indra/newview/skins/default/xui/ru/emoji_categories.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" ?> +<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd"> + <array> + <map> + <key>Name</key> + <string>smileys and emotion</string> + <key>Category</key> + <string>смайлики и люди</string> + </map> + <map> + <key>Name</key> + <string>people and body</string> + <key>Category</key> + <string>тело людей</string> + </map> + <map> + <key>Name</key> + <string>components</string> + <key>Category</key> + <string>компонент</string> + </map> + <map> + <key>Name</key> + <string>animals and nature</string> + <key>Category</key> + <string>животные и природа</string> + </map> + <map> + <key>Name</key> + <string>food and drink</string> + <key>Category</key> + <string>еда и напитки</string> + </map> + <map> + <key>Name</key> + <string>travel and places</string> + <key>Category</key> + <string>путешествия и местности</string> + </map> + <map> + <key>Name</key> + <string>activities</string> + <key>Category</key> + <string>варианты досуга</string> + </map> + <map> + <key>Name</key> + <string>objects</string> + <key>Category</key> + <string>предметы</string> + </map> + <map> + <key>Name</key> + <string>symbols</string> + <key>Category</key> + <string>символы</string> + </map> + </array> +</llsd> diff --git a/indra/newview/skins/default/xui/zh/emoji_categories.xml b/indra/newview/skins/default/xui/zh/emoji_categories.xml new file mode 100644 index 0000000000..fbe6165eeb --- /dev/null +++ b/indra/newview/skins/default/xui/zh/emoji_categories.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" ?> +<llsd xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="llsd.xsd"> + <array> + <map> + <key>Name</key> + <string>smileys and emotion</string> + <key>Category</key> + <string>笑脸</string> + </map> + <map> + <key>Name</key> + <string>people and body</string> + <key>Category</key> + <string>人体</string> + </map> + <map> + <key>Name</key> + <string>components</string> + <key>Category</key> + <string>组件</string> + </map> + <map> + <key>Name</key> + <string>animals and nature</string> + <key>Category</key> + <string>野生动物</string> + </map> + <map> + <key>Name</key> + <string>food and drink</string> + <key>Category</key> + <string>食物飲料</string> + </map> + <map> + <key>Name</key> + <string>travel and places</string> + <key>Category</key> + <string>旅遊地點</string> + </map> + <map> + <key>Name</key> + <string>activities</string> + <key>Category</key> + <string>个人活动</string> + </map> + <map> + <key>Name</key> + <string>objects</string> + <key>Category</key> + <string>物件</string> + </map> + <map> + <key>Name</key> + <string>symbols</string> + <key>Category</key> + <string>人的符号</string> + </map> + </array> +</llsd> |