summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
Diffstat (limited to 'indra')
-rw-r--r--indra/llui/llemojidictionary.cpp424
-rw-r--r--indra/llui/llemojidictionary.h78
-rw-r--r--indra/newview/app_settings/emoji_groups.xml82
-rw-r--r--indra/newview/llfloateremojipicker.cpp849
-rw-r--r--indra/newview/llfloateremojipicker.h102
-rw-r--r--indra/newview/skins/default/xui/da/emoji_categories.xml59
-rw-r--r--indra/newview/skins/default/xui/de/emoji_categories.xml59
-rw-r--r--indra/newview/skins/default/xui/en/emoji_categories.xml59
-rw-r--r--indra/newview/skins/default/xui/en/floater_emoji_picker.xml33
-rw-r--r--indra/newview/skins/default/xui/es/emoji_categories.xml59
-rw-r--r--indra/newview/skins/default/xui/fr/emoji_categories.xml59
-rw-r--r--indra/newview/skins/default/xui/it/emoji_categories.xml59
-rw-r--r--indra/newview/skins/default/xui/ja/emoji_categories.xml59
-rw-r--r--indra/newview/skins/default/xui/pl/emoji_categories.xml59
-rw-r--r--indra/newview/skins/default/xui/pt/emoji_categories.xml59
-rw-r--r--indra/newview/skins/default/xui/ru/emoji_categories.xml59
-rw-r--r--indra/newview/skins/default/xui/zh/emoji_categories.xml59
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>