From 9793308a600c1e1ce35ec727ed6341e7668848ea Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Thu, 6 Jul 2023 23:48:06 +0200 Subject: SL-19951 Organize emoji categories in groups --- indra/newview/app_settings/emoji_groups.xml | 82 ++ indra/newview/llfloateremojipicker.cpp | 849 ++++++++++++--------- indra/newview/llfloateremojipicker.h | 102 +-- .../skins/default/xui/da/emoji_categories.xml | 59 ++ .../skins/default/xui/de/emoji_categories.xml | 59 ++ .../skins/default/xui/en/emoji_categories.xml | 59 ++ .../skins/default/xui/en/floater_emoji_picker.xml | 33 +- .../skins/default/xui/es/emoji_categories.xml | 59 ++ .../skins/default/xui/fr/emoji_categories.xml | 59 ++ .../skins/default/xui/it/emoji_categories.xml | 59 ++ .../skins/default/xui/ja/emoji_categories.xml | 59 ++ .../skins/default/xui/pl/emoji_categories.xml | 59 ++ .../skins/default/xui/pt/emoji_categories.xml | 59 ++ .../skins/default/xui/ru/emoji_categories.xml | 59 ++ .../skins/default/xui/zh/emoji_categories.xml | 59 ++ 15 files changed, 1284 insertions(+), 431 deletions(-) create mode 100644 indra/newview/app_settings/emoji_groups.xml create mode 100644 indra/newview/skins/default/xui/da/emoji_categories.xml create mode 100644 indra/newview/skins/default/xui/de/emoji_categories.xml create mode 100644 indra/newview/skins/default/xui/en/emoji_categories.xml create mode 100644 indra/newview/skins/default/xui/es/emoji_categories.xml create mode 100644 indra/newview/skins/default/xui/fr/emoji_categories.xml create mode 100644 indra/newview/skins/default/xui/it/emoji_categories.xml create mode 100644 indra/newview/skins/default/xui/ja/emoji_categories.xml create mode 100644 indra/newview/skins/default/xui/pl/emoji_categories.xml create mode 100644 indra/newview/skins/default/xui/pt/emoji_categories.xml create mode 100644 indra/newview/skins/default/xui/ru/emoji_categories.xml create mode 100644 indra/newview/skins/default/xui/zh/emoji_categories.xml (limited to 'indra/newview') 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 @@ + + + + + Name + all + Character + 🔍 + + + Character + 😀 + Categories + + smileys and emotion + people and body + + + + Character + 🥬 + Categories + + animals and nature + + + + Character + 🍔 + Categories + + food and drink + + + + Character + 🛩 + Categories + + travel and places + + + + Character + 🏈 + Categories + + activities + + + + Character + 💡 + Categories + + objects + + + + Character + + Categories + + symbols + + + + Name + others + Character + 🌂 + + + Name + skip + Categories + + components + + + + 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("emoji_picker"); - if (!floater) - LL_ERRS() << "Cannot instantiate emoji picker" << LL_ENDL; - return floater; + LLFloaterEmojiPicker* floater = LLFloaterReg::getTypedInstance("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("PreviewEmoji"); - mPreviewEmoji->setClickedCallback([this](LLUICtrl*, const LLSD&) { onPreviewEmojiClick(); }); - - mDescription = getChild("Description"); - mDescription->setVisible(FALSE); - - mCategory = getChild("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("Search"); - mSearch->setKeystrokeCallback([this](LLLineEditor*, void*) { onSearchKeystroke(); }, NULL); - mSearch->setFont(LLViewerChat::getChatFont()); - mSearch->setText(mSearchPattern); - - mEmojiScroll = getChild("EmojiGridContainer"); - mEmojiScroll->setMouseEnterCallback([this](LLUICtrl*, const LLSD&) { onGridMouseEnter(); }); - mEmojiScroll->setMouseLeaveCallback([this](LLUICtrl*, const LLSD&) { onGridMouseLeave(); }); - - mEmojiGrid = getChild("EmojiGrid"); - - fillEmojiGrid(); - - return TRUE; + // Should be initialized first + mPreviewEmoji = getChild("PreviewEmoji"); + mPreviewEmoji->setClickedCallback([this](LLUICtrl*, const LLSD&) { onPreviewEmojiClick(); }); + + mDescription = getChild("Description"); + mDescription->setVisible(FALSE); + + mGroups = getChild("Groups"); + mBadge = getChild("Badge"); + + mSearch = getChild("Search"); + mSearch->setKeystrokeCallback([this](LLLineEditor*, void*) { onSearchKeystroke(); }, NULL); + mSearch->setFont(LLViewerChat::getChatFont()); + mSearch->setText(sSearchPattern); + + mEmojiScroll = getChild("EmojiGridContainer"); + mEmojiScroll->setMouseEnterCallback([this](LLUICtrl*, const LLSD&) { onGridMouseEnter(); }); + mEmojiScroll->setMouseLeaveCallback([this](LLUICtrl*, const LLSD&) { onGridMouseLeave(); }); + + mEmojiGrid = getChild("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& groups = LLEmojiDictionary::instance().getGroups(); + for (const LLEmojiGroup& group : groups) + { + LLButton* button = LLUICtrlFactory::create(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& 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 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& 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 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& 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& 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(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(mHoveredIcon)) - { - mEmojiPickCallback(icon->getEmoji()); - } - } + if (mEmojiPickCallback) + { + if (LLEmojiGridIcon* icon = dynamic_cast(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(ctrl)) + { + button->setUseFontColor(TRUE); + } +} + +void LLFloaterEmojiPicker::onGroupButtonMouseLeave(LLUICtrl* ctrl) +{ + if (LLButton* button = dynamic_cast(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(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(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(ctrl)) - { - icon->setBackgroundVisible(TRUE); + if (LLEmojiGridIcon* icon = dynamic_cast(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(ctrl)) - { - icon->setBackgroundVisible(FALSE); - mPreviewEmoji->setLabel(LLUIString()); - mDescription->setText(LLStringExplicit(""), LLStyle::Params()); - } + if (LLEmojiGridIcon* icon = dynamic_cast(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 pick_callback_t; - typedef boost::function close_callback_t; + // The callback function will be called with an emoji char. + typedef boost::function pick_callback_t; + typedef boost::function 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 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 @@ + + + + + Name + smileys and emotion + Category + smileys and følelser + + + Name + people and body + Category + mennesker and krop + + + Name + components + Category + komponenter + + + Name + animals and nature + Category + dyr and natur + + + Name + food and drink + Category + mad and drikke + + + Name + travel and places + Category + rejser and steder + + + Name + activities + Category + oplevelser + + + Name + objects + Category + objekter + + + Name + symbols + Category + symboler + + + 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 @@ + + + + + Name + smileys and emotion + Category + Smileys and Emotionen + + + Name + people and body + Category + Menschen and Körper + + + Name + components + Category + Komponenten + + + Name + animals and nature + Category + Tiere and Natur + + + Name + food and drink + Category + Essen and Trinken + + + Name + travel and places + Category + Reisen and Orte + + + Name + activities + Category + Aktivitäten + + + Name + objects + Category + Gegenstände + + + Name + symbols + Category + Symbole + + + 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 @@ + + + + + Name + smileys and emotion + Category + smileys and emotion + + + Name + people and body + Category + people and body + + + Name + components + Category + components + + + Name + animals and nature + Category + animals and nature + + + Name + food and drink + Category + food and drink + + + Name + travel and places + Category + travel and places + + + Name + activities + Category + activities + + + Name + objects + Category + objects + + + Name + symbols + Category + symbols + + + 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"> + width="212" /> + width="200" />