From ff7ebf08922293c1623b7bdb8c9923c14fc9db48 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Fri, 14 Apr 2023 07:44:56 +0200 Subject: SL-19575 Create emoji gallery access icon --- indra/newview/llfloateremojipicker.cpp | 143 +++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 indra/newview/llfloateremojipicker.cpp (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp new file mode 100644 index 0000000000..c828a95a59 --- /dev/null +++ b/indra/newview/llfloateremojipicker.cpp @@ -0,0 +1,143 @@ +/** + * @file llfloateremojipicker.cpp + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloateremojipicker.h" + +#include "llfloaterreg.h" +#include "llscrolllistctrl.h" +#include "llscrolllistitem.h" +#include "llemojidictionary.h" + +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); + + S32 width = getColumn(0)->getWidth(); + LLFontGL::getFontEmoji()->render(LLWString(1, mEmoji), 0, rect.mLeft + width / 2, rect.getCenterY(), LLColor4::white, + LLFontGL::HCENTER, LLFontGL::VCENTER, LLFontGL::NORMAL, LLFontGL::DROP_SHADOW_SOFT, 1, S32_MAX, nullptr, false, true); + } + +private: + llwchar mEmoji; +}; + +LLFloaterEmojiPicker* LLFloaterEmojiPicker::getInstance() +{ + LLFloaterEmojiPicker* floater = LLFloaterReg::getTypedInstance("emoji_picker"); + if (!floater) + LL_WARNS() << "Cannot instantiate emoji picker" << LL_ENDL; + return floater; +} + +LLFloaterEmojiPicker* LLFloaterEmojiPicker::showInstance(select_callback_t callback) +{ + LLFloaterEmojiPicker* floater = getInstance(); + if (LLFloaterEmojiPicker* floater = getInstance()) + floater->show(callback); + return floater; +} + +void LLFloaterEmojiPicker::show(select_callback_t callback) +{ + mSelectCallback = callback; + openFloater(mKey); + setFocus(TRUE); +} + +LLFloaterEmojiPicker::LLFloaterEmojiPicker(const LLSD& key) +: LLFloater(key) +{ +} + +BOOL LLFloaterEmojiPicker::postBuild() +{ + if (mEmojis = getChild("Emojis")) + { + mEmojis->setDoubleClickCallback(boost::bind(&LLFloaterEmojiPicker::onSelect, this)); + + mEmojis->clearRows(); + + const std::map& emoji2Descr = LLEmojiDictionary::instance().getEmoji2Descr(); + for (const std::pair& it : emoji2Descr) + { + LLScrollListItem::Params params; + params.columns.add().column("name").value(it.second->Name); + mEmojis->addRow(new LLEmojiScrollListItem(it.first, params), params); + } + } + + return TRUE; +} + +LLFloaterEmojiPicker::~LLFloaterEmojiPicker() +{ + gFocusMgr.releaseFocusIfNeeded( this ); +} + +void LLFloaterEmojiPicker::onSelect() +{ + if (mEmojis && mSelectCallback) + { + if (LLEmojiScrollListItem* item = dynamic_cast(mEmojis->getFirstSelected())) + { + mSelectCallback(item->getEmoji()); + } + } +} + +// virtual +BOOL LLFloaterEmojiPicker::handleKeyHere(KEY key, MASK mask) +{ + if (key == KEY_RETURN && mask == MASK_NONE) + { + onSelect(); + return TRUE; + } + else if (key == KEY_ESCAPE && mask == MASK_NONE) + { + closeFloater(); + return TRUE; + } + + return LLFloater::handleKeyHere(key, mask); +} -- cgit v1.2.3 From 8dad411e9055c32a753e575ccd6142073eb27aae Mon Sep 17 00:00:00 2001 From: Callum Prentice Date: Fri, 14 Apr 2023 09:05:14 -0700 Subject: SL-19575: Emoji gallery - couple of tweaks that mac Clang got sad about --- indra/newview/llfloateremojipicker.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index c828a95a59..a63a9fac4f 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -91,14 +91,14 @@ LLFloaterEmojiPicker::LLFloaterEmojiPicker(const LLSD& key) BOOL LLFloaterEmojiPicker::postBuild() { - if (mEmojis = getChild("Emojis")) + if ((mEmojis = getChild("Emojis"))) { mEmojis->setDoubleClickCallback(boost::bind(&LLFloaterEmojiPicker::onSelect, this)); mEmojis->clearRows(); const std::map& emoji2Descr = LLEmojiDictionary::instance().getEmoji2Descr(); - for (const std::pair& it : emoji2Descr) + for (const std::pair& it : emoji2Descr) { LLScrollListItem::Params params; params.columns.add().column("name").value(it.second->Name); -- cgit v1.2.3 From 97b0ba2a6d2596da867043077e32065653d44f6e Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Wed, 19 Apr 2023 01:39:42 +0200 Subject: SL-19575 LLFloaterEmojiPicker - Add filter by category --- indra/newview/llfloateremojipicker.cpp | 203 +++++++++++++++++++++++++++++---- 1 file changed, 178 insertions(+), 25 deletions(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index a63a9fac4f..5efd2a24c0 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -27,10 +27,18 @@ #include "llfloateremojipicker.h" +#include "llcombobox.h" +#include "llemojidictionary.h" #include "llfloaterreg.h" +#include "lllineeditor.h" #include "llscrolllistctrl.h" #include "llscrolllistitem.h" -#include "llemojidictionary.h" +#include "lltextbox.h" +#include "llviewerchat.h" + +std::string LLFloaterEmojiPicker::mSelectedCategory; +std::string LLFloaterEmojiPicker::mSearchPattern; +int LLFloaterEmojiPicker::mSelectedEmojiIndex; class LLEmojiScrollListItem : public LLScrollListItem { @@ -69,17 +77,18 @@ LLFloaterEmojiPicker* LLFloaterEmojiPicker::getInstance() return floater; } -LLFloaterEmojiPicker* LLFloaterEmojiPicker::showInstance(select_callback_t callback) +LLFloaterEmojiPicker* LLFloaterEmojiPicker::showInstance(pick_callback_t pick_callback, close_callback_t close_callback) { LLFloaterEmojiPicker* floater = getInstance(); if (LLFloaterEmojiPicker* floater = getInstance()) - floater->show(callback); + floater->show(pick_callback, close_callback); return floater; } -void LLFloaterEmojiPicker::show(select_callback_t callback) +void LLFloaterEmojiPicker::show(pick_callback_t pick_callback, close_callback_t close_callback) { - mSelectCallback = callback; + mEmojiPickCallback = pick_callback; + mFloaterCloseCallback = close_callback; openFloater(mKey); setFocus(TRUE); } @@ -91,19 +100,41 @@ LLFloaterEmojiPicker::LLFloaterEmojiPicker(const LLSD& key) BOOL LLFloaterEmojiPicker::postBuild() { - if ((mEmojis = getChild("Emojis"))) + // Should be initialized first + if ((mPreviewEmoji = getChild("PreviewEmoji"))) { - mEmojis->setDoubleClickCallback(boost::bind(&LLFloaterEmojiPicker::onSelect, this)); - - mEmojis->clearRows(); + mPreviewEmoji->setClickedCallback(boost::bind(&LLFloaterEmojiPicker::onPreviewEmojiClick, this)); + } - const std::map& emoji2Descr = LLEmojiDictionary::instance().getEmoji2Descr(); - for (const std::pair& it : emoji2Descr) + if ((mCategory = getChild("Category"))) + { + mCategory->setCommitCallback(boost::bind(&LLFloaterEmojiPicker::onCategoryCommit, this)); + mCategory->setLabel(LLStringExplicit("Choose a category")); + const auto& cat2Descrs = LLEmojiDictionary::instance().getCategory2Descrs(); + mCategory->clearRows(); + for (const auto& item : cat2Descrs) { - LLScrollListItem::Params params; - params.columns.add().column("name").value(it.second->Name); - mEmojis->addRow(new LLEmojiScrollListItem(it.first, params), params); + std::string value = item.first; + std::string name = value; + LLStringUtil::capitalize(name); + mCategory->add(name, value); } + mCategory->setSelectedByValue(mSelectedCategory, true); + } + + if ((mSearch = getChild("Search"))) + { + mSearch->setKeystrokeCallback(boost::bind(&LLFloaterEmojiPicker::onSearchKeystroke, this, _1, _2), NULL); + mSearch->setLabel(LLStringExplicit("Type to search an emoji")); + mSearch->setFont(LLViewerChat::getChatFont()); + mSearch->setText(mSearchPattern); + } + + if ((mEmojis = getChild("Emojis"))) + { + mEmojis->setCommitCallback(boost::bind(&LLFloaterEmojiPicker::onEmojiSelect, this)); + mEmojis->setDoubleClickCallback(boost::bind(&LLFloaterEmojiPicker::onEmojiPick, this)); + fillEmojis(); } return TRUE; @@ -114,30 +145,152 @@ LLFloaterEmojiPicker::~LLFloaterEmojiPicker() gFocusMgr.releaseFocusIfNeeded( this ); } -void LLFloaterEmojiPicker::onSelect() +void LLFloaterEmojiPicker::fillEmojis() +{ + mEmojis->clearRows(); + + const auto& emoji2Descr = LLEmojiDictionary::instance().getEmoji2Descr(); + for (const std::pair& it : emoji2Descr) + { + const LLEmojiDescriptor* descr = it.second; + + if (!mSelectedCategory.empty() && !matchesCategory(descr)) + continue; + + if (!mSearchPattern.empty() && !matchesPattern(descr)) + continue; + + LLScrollListItem::Params params; + params.columns.add().column("name").value(descr->Name); + mEmojis->addRow(new LLEmojiScrollListItem(it.first, params), params); + } + + if (mEmojis->getItemCount()) + { + if (mSelectedEmojiIndex > 0 && mSelectedEmojiIndex < mEmojis->getItemCount()) + mEmojis->selectNthItem(mSelectedEmojiIndex); + else + mEmojis->selectFirstItem(); + + mEmojis->scrollToShowSelected(); + } + else + { + onEmojiEmpty(); + } +} + +bool LLFloaterEmojiPicker::matchesCategory(const LLEmojiDescriptor* descr) +{ + return std::find(descr->Categories.begin(), descr->Categories.end(), mSelectedCategory) != descr->Categories.end(); +} + +bool LLFloaterEmojiPicker::matchesPattern(const LLEmojiDescriptor* descr) +{ + if (descr->Name.find(mSearchPattern) != std::string::npos) + return true; + for (auto shortCode : descr->ShortCodes) + if (shortCode.find(mSearchPattern) != std::string::npos) + return true; + for (auto category : descr->Categories) + if (category.find(mSearchPattern) != std::string::npos) + return true; + return false; +} + +void LLFloaterEmojiPicker::onCategoryCommit() +{ + mSelectedCategory = mCategory->getSelectedValue().asString(); + mSelectedEmojiIndex = 0; + fillEmojis(); +} + +void LLFloaterEmojiPicker::onSearchKeystroke(LLLineEditor* caller, void* user_data) { - if (mEmojis && mSelectCallback) + mSearchPattern = mSearch->getText(); + mSelectedEmojiIndex = 0; + fillEmojis(); +} + +void LLFloaterEmojiPicker::onPreviewEmojiClick() +{ + if (mEmojis && mEmojiPickCallback) { if (LLEmojiScrollListItem* item = dynamic_cast(mEmojis->getFirstSelected())) { - mSelectCallback(item->getEmoji()); + mEmojiPickCallback(item->getEmoji()); } } } -// virtual -BOOL LLFloaterEmojiPicker::handleKeyHere(KEY key, MASK mask) +void LLFloaterEmojiPicker::onEmojiSelect() { - if (key == KEY_RETURN && mask == MASK_NONE) + const LLEmojiScrollListItem* item = dynamic_cast(mEmojis->getFirstSelected()); + if (item) { - onSelect(); - return TRUE; + mSelectedEmojiIndex = mEmojis->getFirstSelectedIndex(); + LLUIString text; + text.insert(0, LLWString(1, item->getEmoji())); + if (mPreviewEmoji) + mPreviewEmoji->setLabel(text); + return; } - else if (key == KEY_ESCAPE && mask == MASK_NONE) + + onEmojiEmpty(); +} + +void LLFloaterEmojiPicker::onEmojiEmpty() +{ + mSelectedEmojiIndex = 0; + if (mPreviewEmoji) + mPreviewEmoji->setLabel(LLUIString()); +} + +void LLFloaterEmojiPicker::onEmojiPick() +{ + if (mEmojis && mEmojiPickCallback) { - closeFloater(); - return TRUE; + if (LLEmojiScrollListItem* item = dynamic_cast(mEmojis->getFirstSelected())) + { + mEmojiPickCallback(item->getEmoji()); + closeFloater(); + } + } +} + +// virtual +BOOL LLFloaterEmojiPicker::handleKeyHere(KEY key, MASK mask) +{ + if (mask == MASK_NONE) + { + switch (key) + { + case KEY_RETURN: + if (mCategory->hasFocus()) + break; + onEmojiPick(); + return TRUE; + case KEY_ESCAPE: + closeFloater(); + return TRUE; + case KEY_UP: + mEmojis->selectPrevItem(); + mEmojis->scrollToShowSelected(); + return TRUE; + case KEY_DOWN: + mEmojis->selectNextItem(); + mEmojis->scrollToShowSelected(); + return TRUE; + } } return LLFloater::handleKeyHere(key, mask); } + +// virtual +void LLFloaterEmojiPicker::closeFloater(bool app_quitting) +{ + LLFloater::closeFloater(app_quitting); + if (mFloaterCloseCallback) + mFloaterCloseCallback(); +} -- cgit v1.2.3 From c2eec5b100a7064f3c737db46f893864a8b29c3a Mon Sep 17 00:00:00 2001 From: Ansariel Date: Sat, 22 Apr 2023 18:38:46 +0200 Subject: Don't hardcode UI text plus layout fixes and removing some needless checks --- indra/newview/llfloateremojipicker.cpp | 54 ++++++++++++++-------------------- 1 file changed, 22 insertions(+), 32 deletions(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index 5efd2a24c0..ab81b2936f 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -80,7 +80,7 @@ LLFloaterEmojiPicker* LLFloaterEmojiPicker::getInstance() LLFloaterEmojiPicker* LLFloaterEmojiPicker::showInstance(pick_callback_t pick_callback, close_callback_t close_callback) { LLFloaterEmojiPicker* floater = getInstance(); - if (LLFloaterEmojiPicker* floater = getInstance()) + if (floater) floater->show(pick_callback, close_callback); return floater; } @@ -101,41 +101,31 @@ LLFloaterEmojiPicker::LLFloaterEmojiPicker(const LLSD& key) BOOL LLFloaterEmojiPicker::postBuild() { // Should be initialized first - if ((mPreviewEmoji = getChild("PreviewEmoji"))) + mPreviewEmoji = getChild("PreviewEmoji"); + mPreviewEmoji->setClickedCallback(boost::bind(&LLFloaterEmojiPicker::onPreviewEmojiClick, this)); + + mCategory = getChild("Category"); + mCategory->setCommitCallback(boost::bind(&LLFloaterEmojiPicker::onCategoryCommit, this)); + const auto& cat2Descrs = LLEmojiDictionary::instance().getCategory2Descrs(); + mCategory->clearRows(); + for (const auto& item : cat2Descrs) { - mPreviewEmoji->setClickedCallback(boost::bind(&LLFloaterEmojiPicker::onPreviewEmojiClick, this)); + std::string value = item.first; + std::string name = value; + LLStringUtil::capitalize(name); + mCategory->add(name, value); } + mCategory->setSelectedByValue(mSelectedCategory, true); - if ((mCategory = getChild("Category"))) - { - mCategory->setCommitCallback(boost::bind(&LLFloaterEmojiPicker::onCategoryCommit, this)); - mCategory->setLabel(LLStringExplicit("Choose a category")); - const auto& cat2Descrs = LLEmojiDictionary::instance().getCategory2Descrs(); - mCategory->clearRows(); - for (const auto& item : cat2Descrs) - { - std::string value = item.first; - std::string name = value; - LLStringUtil::capitalize(name); - mCategory->add(name, value); - } - mCategory->setSelectedByValue(mSelectedCategory, true); - } - - if ((mSearch = getChild("Search"))) - { - mSearch->setKeystrokeCallback(boost::bind(&LLFloaterEmojiPicker::onSearchKeystroke, this, _1, _2), NULL); - mSearch->setLabel(LLStringExplicit("Type to search an emoji")); - mSearch->setFont(LLViewerChat::getChatFont()); - mSearch->setText(mSearchPattern); - } + mSearch = getChild("Search"); + mSearch->setKeystrokeCallback(boost::bind(&LLFloaterEmojiPicker::onSearchKeystroke, this, _1, _2), NULL); + mSearch->setFont(LLViewerChat::getChatFont()); + mSearch->setText(mSearchPattern); - if ((mEmojis = getChild("Emojis"))) - { - mEmojis->setCommitCallback(boost::bind(&LLFloaterEmojiPicker::onEmojiSelect, this)); - mEmojis->setDoubleClickCallback(boost::bind(&LLFloaterEmojiPicker::onEmojiPick, this)); - fillEmojis(); - } + mEmojis = getChild("Emojis"); + mEmojis->setCommitCallback(boost::bind(&LLFloaterEmojiPicker::onEmojiSelect, this)); + mEmojis->setDoubleClickCallback(boost::bind(&LLFloaterEmojiPicker::onEmojiPick, this)); + fillEmojis(); return TRUE; } -- cgit v1.2.3 From fbf5b199f3bd9775bc992609baf6c093177a70d8 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Tue, 25 Apr 2023 00:44:25 +0200 Subject: SL-19575 LLFloaterEmojiPicker - code cleanup and layout fixup --- indra/newview/llfloateremojipicker.cpp | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index ab81b2936f..9d28f7d4dc 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -73,15 +73,14 @@ LLFloaterEmojiPicker* LLFloaterEmojiPicker::getInstance() { LLFloaterEmojiPicker* floater = LLFloaterReg::getTypedInstance("emoji_picker"); if (!floater) - LL_WARNS() << "Cannot instantiate emoji picker" << LL_ENDL; + 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(); - if (floater) - floater->show(pick_callback, close_callback); + floater->show(pick_callback, close_callback); return floater; } @@ -106,9 +105,9 @@ BOOL LLFloaterEmojiPicker::postBuild() mCategory = getChild("Category"); mCategory->setCommitCallback(boost::bind(&LLFloaterEmojiPicker::onCategoryCommit, this)); - const auto& cat2Descrs = LLEmojiDictionary::instance().getCategory2Descrs(); + const LLEmojiDictionary::cat2descrs_map_t& cat2Descrs = LLEmojiDictionary::instance().getCategory2Descrs(); mCategory->clearRows(); - for (const auto& item : cat2Descrs) + for (const LLEmojiDictionary::cat2descrs_item_t& item : cat2Descrs) { std::string value = item.first; std::string name = value; @@ -139,8 +138,8 @@ void LLFloaterEmojiPicker::fillEmojis() { mEmojis->clearRows(); - const auto& emoji2Descr = LLEmojiDictionary::instance().getEmoji2Descr(); - for (const std::pair& it : emoji2Descr) + const LLEmojiDictionary::emoji2descr_map_t& emoji2Descr = LLEmojiDictionary::instance().getEmoji2Descr(); + for (const LLEmojiDictionary::emoji2descr_item_t& it : emoji2Descr) { const LLEmojiDescriptor* descr = it.second; @@ -179,10 +178,10 @@ bool LLFloaterEmojiPicker::matchesPattern(const LLEmojiDescriptor* descr) { if (descr->Name.find(mSearchPattern) != std::string::npos) return true; - for (auto shortCode : descr->ShortCodes) + for (const std::string& shortCode : descr->ShortCodes) if (shortCode.find(mSearchPattern) != std::string::npos) return true; - for (auto category : descr->Categories) + for (const std::string& category : descr->Categories) if (category.find(mSearchPattern) != std::string::npos) return true; return false; @@ -204,7 +203,7 @@ void LLFloaterEmojiPicker::onSearchKeystroke(LLLineEditor* caller, void* user_da void LLFloaterEmojiPicker::onPreviewEmojiClick() { - if (mEmojis && mEmojiPickCallback) + if (mEmojiPickCallback) { if (LLEmojiScrollListItem* item = dynamic_cast(mEmojis->getFirstSelected())) { @@ -221,8 +220,7 @@ void LLFloaterEmojiPicker::onEmojiSelect() mSelectedEmojiIndex = mEmojis->getFirstSelectedIndex(); LLUIString text; text.insert(0, LLWString(1, item->getEmoji())); - if (mPreviewEmoji) - mPreviewEmoji->setLabel(text); + mPreviewEmoji->setLabel(text); return; } @@ -232,13 +230,12 @@ void LLFloaterEmojiPicker::onEmojiSelect() void LLFloaterEmojiPicker::onEmojiEmpty() { mSelectedEmojiIndex = 0; - if (mPreviewEmoji) - mPreviewEmoji->setLabel(LLUIString()); + mPreviewEmoji->setLabel(LLUIString()); } void LLFloaterEmojiPicker::onEmojiPick() { - if (mEmojis && mEmojiPickCallback) + if (mEmojiPickCallback) { if (LLEmojiScrollListItem* item = dynamic_cast(mEmojis->getFirstSelected())) { -- cgit v1.2.3 From 671978e3927bc3ba9fc34008bbb7efd6f07b6c81 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Wed, 17 May 2023 14:28:36 +0200 Subject: SL-19575 Create emoji gallery (fix bug with drawing emojis in chat history) --- indra/newview/llfloateremojipicker.cpp | 38 +++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 10 deletions(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index 9d28f7d4dc..98fe1e7ca1 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -60,9 +60,25 @@ public: { LLScrollListItem::draw(rect, fg_color, hover_color, select_color, highlight_color, column_padding); + LLWString wstr(1, mEmoji); S32 width = getColumn(0)->getWidth(); - LLFontGL::getFontEmoji()->render(LLWString(1, mEmoji), 0, rect.mLeft + width / 2, rect.getCenterY(), LLColor4::white, - LLFontGL::HCENTER, LLFontGL::VCENTER, LLFontGL::NORMAL, LLFontGL::DROP_SHADOW_SOFT, 1, S32_MAX, nullptr, false, true); + 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: @@ -101,13 +117,13 @@ BOOL LLFloaterEmojiPicker::postBuild() { // Should be initialized first mPreviewEmoji = getChild("PreviewEmoji"); - mPreviewEmoji->setClickedCallback(boost::bind(&LLFloaterEmojiPicker::onPreviewEmojiClick, this)); + mPreviewEmoji->setClickedCallback([this](LLUICtrl*, const LLSD&) { onPreviewEmojiClick(); }); mCategory = getChild("Category"); - mCategory->setCommitCallback(boost::bind(&LLFloaterEmojiPicker::onCategoryCommit, this)); + 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) + for (const LLEmojiDictionary::cat2descrs_item_t item : cat2Descrs) { std::string value = item.first; std::string name = value; @@ -117,13 +133,13 @@ BOOL LLFloaterEmojiPicker::postBuild() mCategory->setSelectedByValue(mSelectedCategory, true); mSearch = getChild("Search"); - mSearch->setKeystrokeCallback(boost::bind(&LLFloaterEmojiPicker::onSearchKeystroke, this, _1, _2), NULL); + mSearch->setKeystrokeCallback([this](LLLineEditor*, void*) { onSearchKeystroke(); }, NULL); mSearch->setFont(LLViewerChat::getChatFont()); mSearch->setText(mSearchPattern); mEmojis = getChild("Emojis"); - mEmojis->setCommitCallback(boost::bind(&LLFloaterEmojiPicker::onEmojiSelect, this)); - mEmojis->setDoubleClickCallback(boost::bind(&LLFloaterEmojiPicker::onEmojiPick, this)); + mEmojis->setCommitCallback([this](LLUICtrl*, const LLSD&) { onEmojiSelect(); }); + mEmojis->setDoubleClickCallback([this]() { onEmojiPick(); }); fillEmojis(); return TRUE; @@ -139,7 +155,7 @@ void LLFloaterEmojiPicker::fillEmojis() mEmojis->clearRows(); const LLEmojiDictionary::emoji2descr_map_t& emoji2Descr = LLEmojiDictionary::instance().getEmoji2Descr(); - for (const LLEmojiDictionary::emoji2descr_item_t& it : emoji2Descr) + for (const LLEmojiDictionary::emoji2descr_item_t it : emoji2Descr) { const LLEmojiDescriptor* descr = it.second; @@ -150,6 +166,8 @@ void LLFloaterEmojiPicker::fillEmojis() continue; LLScrollListItem::Params params; + // The following line adds default monochrome view of the emoji (is shown as an example) + //params.columns.add().column("look").value(wstring_to_utf8str(LLWString(1, it.first))); params.columns.add().column("name").value(descr->Name); mEmojis->addRow(new LLEmojiScrollListItem(it.first, params), params); } @@ -194,7 +212,7 @@ void LLFloaterEmojiPicker::onCategoryCommit() fillEmojis(); } -void LLFloaterEmojiPicker::onSearchKeystroke(LLLineEditor* caller, void* user_data) +void LLFloaterEmojiPicker::onSearchKeystroke() { mSearchPattern = mSearch->getText(); mSelectedEmojiIndex = 0; -- cgit v1.2.3 From bb97f3bf03cfa776b09dc2c0929fbb100a11c775 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Thu, 18 May 2023 07:47:54 +0200 Subject: SL-19575 Create emoji gallery (use ::value_type in LLEmojiDictionary typedefs) --- indra/newview/llfloateremojipicker.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index 98fe1e7ca1..7fbaaaaa89 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -123,7 +123,7 @@ BOOL LLFloaterEmojiPicker::postBuild() 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) + for (const LLEmojiDictionary::cat2descrs_item_t& item : cat2Descrs) { std::string value = item.first; std::string name = value; @@ -155,9 +155,9 @@ void LLFloaterEmojiPicker::fillEmojis() mEmojis->clearRows(); const LLEmojiDictionary::emoji2descr_map_t& emoji2Descr = LLEmojiDictionary::instance().getEmoji2Descr(); - for (const LLEmojiDictionary::emoji2descr_item_t it : emoji2Descr) + for (const LLEmojiDictionary::emoji2descr_item_t& item : emoji2Descr) { - const LLEmojiDescriptor* descr = it.second; + const LLEmojiDescriptor* descr = item.second; if (!mSelectedCategory.empty() && !matchesCategory(descr)) continue; @@ -169,7 +169,7 @@ void LLFloaterEmojiPicker::fillEmojis() // The following line adds default monochrome view of the emoji (is shown as an example) //params.columns.add().column("look").value(wstring_to_utf8str(LLWString(1, it.first))); params.columns.add().column("name").value(descr->Name); - mEmojis->addRow(new LLEmojiScrollListItem(it.first, params), params); + mEmojis->addRow(new LLEmojiScrollListItem(item.first, params), params); } if (mEmojis->getItemCount()) -- cgit v1.2.3 From 8bbbce015b6dae1bdafe0bba329463322642ca85 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Tue, 4 Jul 2023 07:38:05 +0200 Subject: SL-19575 Rework emoji picker layout similar to Slack --- indra/newview/llfloateremojipicker.cpp | 375 +++++++++++++++++++++++++++++++-- 1 file changed, 355 insertions(+), 20 deletions(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index 7fbaaaaa89..8c7f38d324 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -30,7 +30,10 @@ #include "llcombobox.h" #include "llemojidictionary.h" #include "llfloaterreg.h" +#include "llkeyboard.h" #include "lllineeditor.h" +#include "llscrollcontainer.h" +#include "llscrollingpanellist.h" #include "llscrolllistctrl.h" #include "llscrolllistitem.h" #include "lltextbox.h" @@ -39,6 +42,7 @@ std::string LLFloaterEmojiPicker::mSelectedCategory; std::string LLFloaterEmojiPicker::mSearchPattern; int LLFloaterEmojiPicker::mSelectedEmojiIndex; +bool LLFloaterEmojiPicker::mUseGrid = true; class LLEmojiScrollListItem : public LLScrollListItem { @@ -85,6 +89,114 @@ private: 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); + } + + virtual void updatePanel(BOOL allow_modify) + { + } + +public: + 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) + { + } + +private: + 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) {} + + 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; +}; + LLFloaterEmojiPicker* LLFloaterEmojiPicker::getInstance() { LLFloaterEmojiPicker* floater = LLFloaterReg::getTypedInstance("emoji_picker"); @@ -102,6 +214,9 @@ LLFloaterEmojiPicker* LLFloaterEmojiPicker::showInstance(pick_callback_t pick_ca void LLFloaterEmojiPicker::show(pick_callback_t pick_callback, close_callback_t close_callback) { + // Temporary solution to support both layouts + mUseGrid = !gKeyboard->getKeyDown(KEY_SHIFT); + mEmojiPickCallback = pick_callback; mFloaterCloseCallback = close_callback; openFloater(mKey); @@ -119,6 +234,9 @@ BOOL LLFloaterEmojiPicker::postBuild() 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(); @@ -137,14 +255,33 @@ BOOL LLFloaterEmojiPicker::postBuild() mSearch->setFont(LLViewerChat::getChatFont()); mSearch->setText(mSearchPattern); - mEmojis = getChild("Emojis"); - mEmojis->setCommitCallback([this](LLUICtrl*, const LLSD&) { onEmojiSelect(); }); - mEmojis->setDoubleClickCallback([this]() { onEmojiPick(); }); + mEmojiList = getChild("EmojiList"); + mEmojiList->setCommitCallback([this](LLUICtrl*, const LLSD&) { onEmojiSelect(); }); + mEmojiList->setDoubleClickCallback([this]() { onEmojiPick(); }); + mEmojiList->setVisible(!mUseGrid); + + mEmojiScroll = getChild("EmojiGridContainer"); + mEmojiScroll->setVisible(mUseGrid); + mEmojiScroll->setMouseEnterCallback([this](LLUICtrl*, const LLSD&) { onGridMouseEnter(); }); + mEmojiScroll->setMouseLeaveCallback([this](LLUICtrl*, const LLSD&) { onGridMouseLeave(); }); + + mEmojiGrid = getChild("EmojiGrid"); + fillEmojis(); return TRUE; } +void LLFloaterEmojiPicker::dirtyRect() +{ + super::dirtyRect(); + + if (mUseGrid && mEmojiScroll && mEmojiScroll->getRect().getWidth() != mRecentGridWidth) + { + fillEmojiGrid(); + } +} + LLFloaterEmojiPicker::~LLFloaterEmojiPicker() { gFocusMgr.releaseFocusIfNeeded( this ); @@ -152,7 +289,15 @@ LLFloaterEmojiPicker::~LLFloaterEmojiPicker() void LLFloaterEmojiPicker::fillEmojis() { - mEmojis->clearRows(); + if (mUseGrid) + fillEmojiGrid(); + else + fillEmojiList(); +} + +void LLFloaterEmojiPicker::fillEmojiList() +{ + mEmojiList->clearRows(); const LLEmojiDictionary::emoji2descr_map_t& emoji2Descr = LLEmojiDictionary::instance().getEmoji2Descr(); for (const LLEmojiDictionary::emoji2descr_item_t& item : emoji2Descr) @@ -169,17 +314,17 @@ void LLFloaterEmojiPicker::fillEmojis() // The following line adds default monochrome view of the emoji (is shown as an example) //params.columns.add().column("look").value(wstring_to_utf8str(LLWString(1, it.first))); params.columns.add().column("name").value(descr->Name); - mEmojis->addRow(new LLEmojiScrollListItem(item.first, params), params); + mEmojiList->addRow(new LLEmojiScrollListItem(item.first, params), params); } - if (mEmojis->getItemCount()) + if (mEmojiList->getItemCount()) { - if (mSelectedEmojiIndex > 0 && mSelectedEmojiIndex < mEmojis->getItemCount()) - mEmojis->selectNthItem(mSelectedEmojiIndex); + if (mSelectedEmojiIndex > 0 && mSelectedEmojiIndex < mEmojiList->getItemCount()) + mEmojiList->selectNthItem(mSelectedEmojiIndex); else - mEmojis->selectFirstItem(); + mEmojiList->selectFirstItem(); - mEmojis->scrollToShowSelected(); + mEmojiList->scrollToShowSelected(); } else { @@ -187,6 +332,103 @@ void LLFloaterEmojiPicker::fillEmojis() } } +void LLFloaterEmojiPicker::fillEmojiGrid() +{ + mEmojiGrid->clearPanels(); + + S32 scrollbarSize = mEmojiScroll->getSize(); + if (scrollbarSize < 0) + { + static LLUICachedControl scrollbar_size_control("UIScrollbarSize", 0); + scrollbarSize = scrollbar_size_control; + } + + mRecentGridWidth = mEmojiScroll->getRect().getWidth(); + const S32 clientWidth = mRecentGridWidth - scrollbarSize - mEmojiScroll->getBorderWidth() * 2; + if (mEmojiGrid->getRect().getWidth() != clientWidth) + { + LLRect rect = mEmojiGrid->getRect(); + rect.mRight = rect.mLeft + clientWidth; + mEmojiGrid->setRect(rect); + } + + 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)); + + 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); + + 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->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); + } + } + + onEmojiEmpty(); +} + bool LLFloaterEmojiPicker::matchesCategory(const LLEmojiDescriptor* descr) { return std::find(descr->Categories.begin(), descr->Categories.end(), mSelectedCategory) != descr->Categories.end(); @@ -223,19 +465,85 @@ void LLFloaterEmojiPicker::onPreviewEmojiClick() { if (mEmojiPickCallback) { - if (LLEmojiScrollListItem* item = dynamic_cast(mEmojis->getFirstSelected())) + if (mUseGrid) { - mEmojiPickCallback(item->getEmoji()); + if (LLEmojiGridIcon* icon = dynamic_cast(mHoveredIcon)) + { + mEmojiPickCallback(icon->getEmoji()); + } + } + else + { + if (LLEmojiScrollListItem* item = dynamic_cast(mEmojiList->getFirstSelected())) + { + mEmojiPickCallback(item->getEmoji()); + } + } + } +} + +void LLFloaterEmojiPicker::onGridMouseEnter() +{ + 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); +} + +void LLFloaterEmojiPicker::onEmojiMouseEnter(LLUICtrl* ctrl) +{ + if (ctrl) + { + if (mHoveredIcon && mHoveredIcon != ctrl) + { + unselectGridIcon(mHoveredIcon); + } + + selectGridIcon(ctrl); + + mHoveredIcon = ctrl; + } +} + +void LLFloaterEmojiPicker::onEmojiMouseLeave(LLUICtrl* 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(); + } } } } void LLFloaterEmojiPicker::onEmojiSelect() { - const LLEmojiScrollListItem* item = dynamic_cast(mEmojis->getFirstSelected()); - if (item) + if (LLEmojiScrollListItem* item = dynamic_cast(mEmojiList->getFirstSelected())) { - mSelectedEmojiIndex = mEmojis->getFirstSelectedIndex(); + mSelectedEmojiIndex = mEmojiList->getFirstSelectedIndex(); LLUIString text; text.insert(0, LLWString(1, item->getEmoji())); mPreviewEmoji->setLabel(text); @@ -255,7 +563,7 @@ void LLFloaterEmojiPicker::onEmojiPick() { if (mEmojiPickCallback) { - if (LLEmojiScrollListItem* item = dynamic_cast(mEmojis->getFirstSelected())) + if (LLEmojiScrollListItem* item = dynamic_cast(mEmojiList->getFirstSelected())) { mEmojiPickCallback(item->getEmoji()); closeFloater(); @@ -263,6 +571,31 @@ void LLFloaterEmojiPicker::onEmojiPick() } } +void LLFloaterEmojiPicker::selectGridIcon(LLUICtrl* ctrl) +{ + if (LLEmojiGridIcon* icon = dynamic_cast(ctrl)) + { + icon->setBackgroundVisible(TRUE); + + LLUIString text; + text.insert(0, icon->getText()); + mPreviewEmoji->setLabel(text); + + 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()); + } +} + // virtual BOOL LLFloaterEmojiPicker::handleKeyHere(KEY key, MASK mask) { @@ -279,12 +612,12 @@ BOOL LLFloaterEmojiPicker::handleKeyHere(KEY key, MASK mask) closeFloater(); return TRUE; case KEY_UP: - mEmojis->selectPrevItem(); - mEmojis->scrollToShowSelected(); + mEmojiList->selectPrevItem(); + mEmojiList->scrollToShowSelected(); return TRUE; case KEY_DOWN: - mEmojis->selectNextItem(); - mEmojis->scrollToShowSelected(); + mEmojiList->selectNextItem(); + mEmojiList->scrollToShowSelected(); return TRUE; } } @@ -297,5 +630,7 @@ void LLFloaterEmojiPicker::closeFloater(bool app_quitting) { LLFloater::closeFloater(app_quitting); if (mFloaterCloseCallback) + { mFloaterCloseCallback(); + } } -- cgit v1.2.3 From 0414fada8163409adf95f9d7c75a7db5f0952103 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Wed, 5 Jul 2023 12:46:42 +0200 Subject: macos build fix --- indra/newview/llfloateremojipicker.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index 8c7f38d324..67d5d8ea6b 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -100,9 +100,7 @@ public: addChild(mList); } - virtual void updatePanel(BOOL allow_modify) - { - } + virtual void updatePanel(BOOL allow_modify) override {} public: LLScrollingPanelList* mList; @@ -140,9 +138,7 @@ public: true); // use_color } - virtual void updatePanel(BOOL allow_modify) - { - } + virtual void updatePanel(BOOL allow_modify) override {} private: const LLWString mText; @@ -183,7 +179,7 @@ public: true); // use_color } - virtual void updatePanel(BOOL allow_modify) {} + virtual void updatePanel(BOOL allow_modify) override {} llwchar getEmoji() const { return mEmoji; } LLWString getText() const { return mText; } -- cgit v1.2.3 From 18039655ed91e87db1aca9f4d782e65725e30f12 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Wed, 5 Jul 2023 17:44:27 +0200 Subject: SL-19951 Make EmojiFloater UI design looking similar to Slack - bg colors --- indra/newview/llfloateremojipicker.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index 67d5d8ea6b..e620d507ec 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -366,6 +366,18 @@ void LLFloaterEmojiPicker::fillEmojiGrid() 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; @@ -395,6 +407,8 @@ void LLFloaterEmojiPicker::fillEmojiGrid() 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); -- cgit v1.2.3 From deb394e2075e031bfe1472fdc3bc638360683c85 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Thu, 6 Jul 2023 09:33:22 +0200 Subject: SL-19951 Delete unused UI elements (old design) --- indra/newview/llfloateremojipicker.cpp | 153 ++++++--------------------------- 1 file changed, 24 insertions(+), 129 deletions(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index e620d507ec..e94fc203f0 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -41,8 +41,6 @@ std::string LLFloaterEmojiPicker::mSelectedCategory; std::string LLFloaterEmojiPicker::mSearchPattern; -int LLFloaterEmojiPicker::mSelectedEmojiIndex; -bool LLFloaterEmojiPicker::mUseGrid = true; class LLEmojiScrollListItem : public LLScrollListItem { @@ -210,9 +208,6 @@ LLFloaterEmojiPicker* LLFloaterEmojiPicker::showInstance(pick_callback_t pick_ca void LLFloaterEmojiPicker::show(pick_callback_t pick_callback, close_callback_t close_callback) { - // Temporary solution to support both layouts - mUseGrid = !gKeyboard->getKeyDown(KEY_SHIFT); - mEmojiPickCallback = pick_callback; mFloaterCloseCallback = close_callback; openFloater(mKey); @@ -251,19 +246,13 @@ BOOL LLFloaterEmojiPicker::postBuild() mSearch->setFont(LLViewerChat::getChatFont()); mSearch->setText(mSearchPattern); - mEmojiList = getChild("EmojiList"); - mEmojiList->setCommitCallback([this](LLUICtrl*, const LLSD&) { onEmojiSelect(); }); - mEmojiList->setDoubleClickCallback([this]() { onEmojiPick(); }); - mEmojiList->setVisible(!mUseGrid); - mEmojiScroll = getChild("EmojiGridContainer"); - mEmojiScroll->setVisible(mUseGrid); mEmojiScroll->setMouseEnterCallback([this](LLUICtrl*, const LLSD&) { onGridMouseEnter(); }); mEmojiScroll->setMouseLeaveCallback([this](LLUICtrl*, const LLSD&) { onGridMouseLeave(); }); mEmojiGrid = getChild("EmojiGrid"); - fillEmojis(); + fillEmojiGrid(); return TRUE; } @@ -272,7 +261,7 @@ void LLFloaterEmojiPicker::dirtyRect() { super::dirtyRect(); - if (mUseGrid && mEmojiScroll && mEmojiScroll->getRect().getWidth() != mRecentGridWidth) + if (mEmojiScroll && mEmojiScroll->getRect().getWidth() != mRecentGridWidth) { fillEmojiGrid(); } @@ -283,54 +272,9 @@ LLFloaterEmojiPicker::~LLFloaterEmojiPicker() gFocusMgr.releaseFocusIfNeeded( this ); } -void LLFloaterEmojiPicker::fillEmojis() -{ - if (mUseGrid) - fillEmojiGrid(); - else - fillEmojiList(); -} - -void LLFloaterEmojiPicker::fillEmojiList() -{ - mEmojiList->clearRows(); - - const LLEmojiDictionary::emoji2descr_map_t& emoji2Descr = LLEmojiDictionary::instance().getEmoji2Descr(); - for (const LLEmojiDictionary::emoji2descr_item_t& item : emoji2Descr) - { - const LLEmojiDescriptor* descr = item.second; - - if (!mSelectedCategory.empty() && !matchesCategory(descr)) - continue; - - if (!mSearchPattern.empty() && !matchesPattern(descr)) - continue; - - LLScrollListItem::Params params; - // The following line adds default monochrome view of the emoji (is shown as an example) - //params.columns.add().column("look").value(wstring_to_utf8str(LLWString(1, it.first))); - params.columns.add().column("name").value(descr->Name); - mEmojiList->addRow(new LLEmojiScrollListItem(item.first, params), params); - } - - if (mEmojiList->getItemCount()) - { - if (mSelectedEmojiIndex > 0 && mSelectedEmojiIndex < mEmojiList->getItemCount()) - mEmojiList->selectNthItem(mSelectedEmojiIndex); - else - mEmojiList->selectFirstItem(); - - mEmojiList->scrollToShowSelected(); - } - else - { - onEmojiEmpty(); - } -} - void LLFloaterEmojiPicker::fillEmojiGrid() { - mEmojiGrid->clearPanels(); + mRecentGridWidth = mEmojiScroll->getRect().getWidth(); S32 scrollbarSize = mEmojiScroll->getSize(); if (scrollbarSize < 0) @@ -339,8 +283,22 @@ void LLFloaterEmojiPicker::fillEmojiGrid() scrollbarSize = scrollbar_size_control; } - mRecentGridWidth = mEmojiScroll->getRect().getWidth(); 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(); @@ -348,12 +306,6 @@ void LLFloaterEmojiPicker::fillEmojiGrid() mEmojiGrid->setRect(rect); } - 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)); - LLPanel::Params row_panel_params; row_panel_params.rect = LLRect(0, iconSize, rowWidth, 0); @@ -435,8 +387,6 @@ void LLFloaterEmojiPicker::fillEmojiGrid() listCategory(mSelectedCategory, item->second, FALSE); } } - - onEmojiEmpty(); } bool LLFloaterEmojiPicker::matchesCategory(const LLEmojiDescriptor* descr) @@ -460,34 +410,24 @@ bool LLFloaterEmojiPicker::matchesPattern(const LLEmojiDescriptor* descr) void LLFloaterEmojiPicker::onCategoryCommit() { mSelectedCategory = mCategory->getSelectedValue().asString(); - mSelectedEmojiIndex = 0; - fillEmojis(); + mRecentMaxIcons = 0; + fillEmojiGrid(); } void LLFloaterEmojiPicker::onSearchKeystroke() { mSearchPattern = mSearch->getText(); - mSelectedEmojiIndex = 0; - fillEmojis(); + mRecentMaxIcons = 0; + fillEmojiGrid(); } void LLFloaterEmojiPicker::onPreviewEmojiClick() { if (mEmojiPickCallback) { - if (mUseGrid) + if (LLEmojiGridIcon* icon = dynamic_cast(mHoveredIcon)) { - if (LLEmojiGridIcon* icon = dynamic_cast(mHoveredIcon)) - { - mEmojiPickCallback(icon->getEmoji()); - } - } - else - { - if (LLEmojiScrollListItem* item = dynamic_cast(mEmojiList->getFirstSelected())) - { - mEmojiPickCallback(item->getEmoji()); - } + mEmojiPickCallback(icon->getEmoji()); } } } @@ -549,38 +489,6 @@ void LLFloaterEmojiPicker::onEmojiMouseClick(LLUICtrl* ctrl, MASK mask) } } -void LLFloaterEmojiPicker::onEmojiSelect() -{ - if (LLEmojiScrollListItem* item = dynamic_cast(mEmojiList->getFirstSelected())) - { - mSelectedEmojiIndex = mEmojiList->getFirstSelectedIndex(); - LLUIString text; - text.insert(0, LLWString(1, item->getEmoji())); - mPreviewEmoji->setLabel(text); - return; - } - - onEmojiEmpty(); -} - -void LLFloaterEmojiPicker::onEmojiEmpty() -{ - mSelectedEmojiIndex = 0; - mPreviewEmoji->setLabel(LLUIString()); -} - -void LLFloaterEmojiPicker::onEmojiPick() -{ - if (mEmojiPickCallback) - { - if (LLEmojiScrollListItem* item = dynamic_cast(mEmojiList->getFirstSelected())) - { - mEmojiPickCallback(item->getEmoji()); - closeFloater(); - } - } -} - void LLFloaterEmojiPicker::selectGridIcon(LLUICtrl* ctrl) { if (LLEmojiGridIcon* icon = dynamic_cast(ctrl)) @@ -613,22 +521,9 @@ BOOL LLFloaterEmojiPicker::handleKeyHere(KEY key, MASK mask) { switch (key) { - case KEY_RETURN: - if (mCategory->hasFocus()) - break; - onEmojiPick(); - return TRUE; case KEY_ESCAPE: closeFloater(); return TRUE; - case KEY_UP: - mEmojiList->selectPrevItem(); - mEmojiList->scrollToShowSelected(); - return TRUE; - case KEY_DOWN: - mEmojiList->selectNextItem(); - mEmojiList->scrollToShowSelected(); - return TRUE; } } -- cgit v1.2.3 From 4abecaa04bd003136ed027e3892a2ca13d895936 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Thu, 6 Jul 2023 11:17:20 +0200 Subject: SL-19951 Delete unused code --- indra/newview/llfloateremojipicker.cpp | 8 -------- 1 file changed, 8 deletions(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index e94fc203f0..f63062f03a 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -389,11 +389,6 @@ void LLFloaterEmojiPicker::fillEmojiGrid() } } -bool LLFloaterEmojiPicker::matchesCategory(const LLEmojiDescriptor* descr) -{ - return std::find(descr->Categories.begin(), descr->Categories.end(), mSelectedCategory) != descr->Categories.end(); -} - bool LLFloaterEmojiPicker::matchesPattern(const LLEmojiDescriptor* descr) { if (descr->Name.find(mSearchPattern) != std::string::npos) @@ -401,9 +396,6 @@ bool LLFloaterEmojiPicker::matchesPattern(const LLEmojiDescriptor* descr) for (const std::string& shortCode : descr->ShortCodes) if (shortCode.find(mSearchPattern) != std::string::npos) return true; - for (const std::string& category : descr->Categories) - if (category.find(mSearchPattern) != std::string::npos) - return true; return false; } -- cgit v1.2.3 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/llfloateremojipicker.cpp | 849 ++++++++++++++++++--------------- 1 file changed, 476 insertions(+), 373 deletions(-) (limited to 'indra/newview/llfloateremojipicker.cpp') 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(); + } } -- cgit v1.2.3 From e7f2fbf2a49aa4bea22889db545f08ff6d0b4921 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Fri, 7 Jul 2023 01:08:01 +0200 Subject: SL-19951 Highlight the selected tab by colored icon --- indra/newview/llfloateremojipicker.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index 9194a49c45..02982bf63a 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -292,6 +292,7 @@ void LLFloaterEmojiPicker::fillGroups() if (mGroupButtons.size() == sSelectedGroupIndex) { button->setToggleState(TRUE); + button->setUseFontColor(TRUE); } mGroupButtons.push_back(button); @@ -479,9 +480,11 @@ void LLFloaterEmojiPicker::onGroupButtonClick(LLUICtrl* ctrl) if (it == mGroupButtons.end()) return; + mGroupButtons[sSelectedGroupIndex]->setUseFontColor(FALSE); mGroupButtons[sSelectedGroupIndex]->setToggleState(FALSE); sSelectedGroupIndex = it - mGroupButtons.begin(); mGroupButtons[sSelectedGroupIndex]->setToggleState(TRUE); + mGroupButtons[sSelectedGroupIndex]->setUseFontColor(TRUE); LLRect rect = mBadge->getRect(); rect.mLeft = button->getRect().mLeft; @@ -538,7 +541,7 @@ void LLFloaterEmojiPicker::onGroupButtonMouseLeave(LLUICtrl* ctrl) { if (LLButton* button = dynamic_cast(ctrl)) { - button->setUseFontColor(FALSE); + button->setUseFontColor(button->getToggleState()); } } -- cgit v1.2.3 From 16f0329d184f62437c296483143aef72f1aaa284 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Fri, 7 Jul 2023 23:35:01 +0200 Subject: SL-19951 Collect used icons in a special group 'Recently used' --- indra/newview/llfloateremojipicker.cpp | 245 ++++++++++++++++++++++++++++++++- 1 file changed, 238 insertions(+), 7 deletions(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index 02982bf63a..08929b05f5 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -27,6 +27,7 @@ #include "llfloateremojipicker.h" +#include "llappviewer.h" #include "llbutton.h" #include "llcombobox.h" #include "llemojidictionary.h" @@ -37,11 +38,27 @@ #include "llscrollingpanellist.h" #include "llscrolllistctrl.h" #include "llscrolllistitem.h" +#include "llsdserialize.h" #include "lltextbox.h" #include "llviewerchat.h" -size_t LLFloaterEmojiPicker::sSelectedGroupIndex; -std::string LLFloaterEmojiPicker::sSearchPattern; +namespace { +// The following variables and constants are used for storing the floater state +// between different lifecycles of the floater and different sissions of the viewer + +// Floater state related variables +static U32 sSelectedGroupIndex = 0; +static std::string sSearchPattern; +static std::list sRecentlyUsed; +static std::list> sFrequentlyUsed; + +// State file related values +static std::string sStateFileName; +static const std::string sKeySelectedGroupIndex("SelectedGroupIndex"); +static const std::string sKeySearchPattern("SearchPattern"); +static const std::string sKeyRecentlyUsed("RecentlyUsed"); +static const std::string sKeyFrequentlyUsed("FrequentlyUsed"); +} class LLEmojiScrollListItem : public LLScrollListItem { @@ -146,7 +163,10 @@ private: class LLEmojiGridIcon : public LLScrollingPanel { public: - LLEmojiGridIcon(const LLPanel::Params& panel_params, const LLEmojiDescriptor* descr, std::string category) + LLEmojiGridIcon( + const LLPanel::Params& panel_params + , const LLEmojiDescriptor* descr + , std::string category) : LLScrollingPanel(panel_params) , mEmoji(descr->Character) , mText(LLWString(1, mEmoji)) @@ -218,6 +238,7 @@ void LLFloaterEmojiPicker::show(pick_callback_t pick_callback, close_callback_t LLFloaterEmojiPicker::LLFloaterEmojiPicker(const LLSD& key) : LLFloater(key) { + loadState(); } BOOL LLFloaterEmojiPicker::postBuild() @@ -269,7 +290,6 @@ void LLFloaterEmojiPicker::fillGroups() { LLButton::Params params; params.font = LLFontGL::getFontEmoji(); - //params.use_font_color = true; LLRect rect; rect.mTop = mGroups->getRect().getHeight(); @@ -387,12 +407,17 @@ void LLFloaterEmojiPicker::fillEmojis(bool fromResize) static constexpr U32 bgcolorCount = sizeof(bgcolors) / sizeof(*bgcolors); - auto listCategory = [&](std::string category, const std::vector& emojis) + auto listCategory = [&](std::string category, const std::vector& emojis, int maxRows = 0) { + int rowCount = 0; int iconIndex = 0; bool showDivider = true; + bool mixedFolder = maxRows; LLEmojiGridRow* row = nullptr; - LLStringUtil::capitalize(category); + if (!mixedFolder) + { + LLStringUtil::capitalize(category); + } for (const LLEmojiDescriptor* descr : emojis) { if (sSearchPattern.empty() || matchesPattern(descr)) @@ -408,12 +433,14 @@ void LLFloaterEmojiPicker::fillEmojis(bool fromResize) // Place a new row each (maxIcons) icons if (!(iconIndex % maxIcons)) { + if (maxRows && ++rowCount > maxRows) + break; 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); + LLEmojiGridIcon* icon = new LLEmojiGridIcon(icon_params, descr, mixedFolder ? LLStringUtil::capitalize(descr->Category) : 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); }); @@ -428,9 +455,32 @@ void LLFloaterEmojiPicker::fillEmojis(bool fromResize) }; const std::vector& groups = LLEmojiDictionary::instance().getGroups(); + const LLEmojiDictionary::emoji2descr_map_t& emoji2descr = LLEmojiDictionary::instance().getEmoji2Descr(); const LLEmojiDictionary::cat2descrs_map_t& category2Descr = LLEmojiDictionary::instance().getCategory2Descrs(); if (!sSelectedGroupIndex) { + std::vector recentlyUsed; + for (llwchar emoji : sRecentlyUsed) + { + auto it = emoji2descr.find(emoji); + if (it != emoji2descr.end()) + { + recentlyUsed.push_back(it->second); + } + } + listCategory(getString("title_for_recently_used"), recentlyUsed, 1); + + std::vector frequentlyUsed; + for (auto& emoji : sFrequentlyUsed) + { + auto it = emoji2descr.find(emoji.first); + if (it != emoji2descr.end()) + { + frequentlyUsed.push_back(it->second); + } + } + listCategory(getString("title_for_frequently_used"), frequentlyUsed, 1); + // List all groups for (const LLEmojiGroup& group : groups) { @@ -577,6 +627,7 @@ void LLFloaterEmojiPicker::onEmojiMouseClick(LLUICtrl* ctrl, MASK mask) { if (LLEmojiGridIcon* icon = dynamic_cast(ctrl)) { + onEmojiUsed(icon->getEmoji()); mPreviewEmoji->handleAnyMouseClick(0, 0, 0, EMouseClickType::CLICK_LEFT, TRUE); mPreviewEmoji->handleAnyMouseClick(0, 0, 0, EMouseClickType::CLICK_LEFT, FALSE); if (!(mask & 4)) @@ -631,9 +682,189 @@ BOOL LLFloaterEmojiPicker::handleKeyHere(KEY key, MASK mask) // virtual void LLFloaterEmojiPicker::closeFloater(bool app_quitting) { + saveState(); LLFloater::closeFloater(app_quitting); if (mFloaterCloseCallback) { mFloaterCloseCallback(); } } + +void LLFloaterEmojiPicker::onEmojiUsed(llwchar emoji) +{ + // Update sRecentlyUsed + auto itr = std::find(sRecentlyUsed.begin(), sRecentlyUsed.end(), emoji); + if (itr == sRecentlyUsed.end()) + { + sRecentlyUsed.push_front(emoji); + } + else if (itr != sRecentlyUsed.begin()) + { + sRecentlyUsed.erase(itr); + sRecentlyUsed.push_front(emoji); + } + + // Increment and reorder sFrequentlyUsed + auto itf = sFrequentlyUsed.begin(); + while (itf != sFrequentlyUsed.end()) + { + if (itf->first == emoji) + { + itf->second++; + while (itf != sFrequentlyUsed.begin()) + { + auto prior = itf; + prior--; + if (prior->second > itf->second) + break; + prior->swap(*itf); + itf = prior; + } + break; + } + itf++; + } + // Append new if not found + if (itf == sFrequentlyUsed.end()) + sFrequentlyUsed.push_back(std::make_pair(emoji, 1)); +} + +void LLFloaterEmojiPicker::loadState() +{ + if (!sStateFileName.empty()) + return; // Already loaded + + sStateFileName = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "emoji_floater_state.xml"); + + llifstream file; + file.open(sStateFileName.c_str()); + if (!file.is_open()) + { + LL_WARNS() << "Emoji floater state file is missing or inaccessible: " << sStateFileName << LL_ENDL; + return; + } + + LLSD state; + LLSDSerialize::fromXML(state, file); + if (state.isUndefined()) + { + LL_WARNS() << "Emoji floater state file is missing or ill-formed: " << sStateFileName << LL_ENDL; + return; + } + + sSelectedGroupIndex = state[sKeySelectedGroupIndex].asInteger(); + + sSearchPattern = state[sKeySearchPattern].asString(); + + // Load and parse sRecentlyUsed + std::string recentlyUsed = state[sKeyRecentlyUsed]; + std::vector rtokens = LLStringUtil::getTokens(recentlyUsed, ","); + int maxCountR = 20; + for (const std::string& token : rtokens) + { + llwchar emoji = (llwchar)atoi(token.c_str()); + if (std::find(sRecentlyUsed.begin(), sRecentlyUsed.end(), emoji) == sRecentlyUsed.end()) + { + sRecentlyUsed.push_back(emoji); + if (!--maxCountR) + break; + } + } + + // Load and parse sFrequentlyUsed + std::string frequentlyUsed = state[sKeyFrequentlyUsed]; + std::vector ftokens = LLStringUtil::getTokens(frequentlyUsed, ","); + int maxCountF = 20; + for (const std::string& token : ftokens) + { + std::vector pair = LLStringUtil::getTokens(token, ":"); + if (pair.size() == 2) + { + llwchar emoji = (llwchar)atoi(pair[0].c_str()); + if (emoji) + { + U32 count = atoi(pair[1].c_str()); + auto it = std::find_if(sFrequentlyUsed.begin(), sFrequentlyUsed.end(), + [emoji](std::pair& it) { return it.first == emoji; }); + if (it != sFrequentlyUsed.end()) + { + it->second += count; + } + else + { + sFrequentlyUsed.push_back(std::make_pair(emoji, count)); + if (!--maxCountF) + break; + } + } + } + } + + // Normalize by minimum + if (!sFrequentlyUsed.empty()) + { + U32 delta = sFrequentlyUsed.back().second; + for (auto& it : sFrequentlyUsed) + { + it.second = std::max((U32)0, it.second - delta); + } + } +} + +void LLFloaterEmojiPicker::saveState() +{ + if (sStateFileName.empty()) + return; // Not loaded + + if (LLAppViewer::instance()->isSecondInstance()) + return; // Not allowed + + LLSD state = LLSD::emptyMap(); + + if (sSelectedGroupIndex) + { + state[sKeySelectedGroupIndex] = (int)sSelectedGroupIndex; + } + + if (!sSearchPattern.empty()) + { + state[sKeySearchPattern] = sSearchPattern; + } + + if (!sRecentlyUsed.empty()) + { + U32 maxCount = 20; + std::string recentlyUsed; + for (llwchar emoji : sRecentlyUsed) + { + if (!recentlyUsed.empty()) + recentlyUsed += ","; + char buffer[32]; + sprintf(buffer, "%u", (U32)emoji); + recentlyUsed += buffer; + if (!--maxCount) + break; + } + state[sKeyRecentlyUsed] = recentlyUsed; + } + + if (!sFrequentlyUsed.empty()) + { + U32 maxCount = 20; + std::string frequentlyUsed; + for (auto& it : sFrequentlyUsed) + { + if (!frequentlyUsed.empty()) + frequentlyUsed += ","; + char buffer[32]; + sprintf(buffer, "%u:%u", (U32)it.first, (U32)it.second); + frequentlyUsed += buffer; + if (!--maxCount) + break; + } + state[sKeyFrequentlyUsed] = frequentlyUsed; + } + + llofstream stream(sStateFileName.c_str()); + LLSDSerialize::toPrettyXML(state, stream); +} -- cgit v1.2.3 From 1fe007abef6eeceefb0dc720b4a5ecb4505ede88 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Thu, 13 Jul 2023 21:18:22 +0200 Subject: SL-20001 EmojiPicker - make the preview to be a panel instead of a button --- indra/newview/llfloateremojipicker.cpp | 286 +++++++++++++++++++-------------- 1 file changed, 161 insertions(+), 125 deletions(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index 08929b05f5..808aca4bf4 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -48,63 +48,18 @@ namespace { // Floater state related variables static U32 sSelectedGroupIndex = 0; -static std::string sSearchPattern; +static std::string sFilterPattern; static std::list sRecentlyUsed; static std::list> sFrequentlyUsed; // State file related values static std::string sStateFileName; static const std::string sKeySelectedGroupIndex("SelectedGroupIndex"); -static const std::string sKeySearchPattern("SearchPattern"); +static const std::string sKeyFilterPattern("FilterPattern"); static const std::string sKeyRecentlyUsed("RecentlyUsed"); static const std::string sKeyFrequentlyUsed("FrequentlyUsed"); } -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 - } - -private: - llwchar mEmoji; -}; - class LLEmojiGridRow : public LLScrollingPanel { public: @@ -137,7 +92,7 @@ public: F32 x = 4; // padding-left F32 y = getRect().getHeight() / 2; - LLFontGL::getFontSansSerifBold()->render( + LLFontGL::getFontSansSerif()->render( mText, // wstr 0, // begin_offset x, // x @@ -168,10 +123,8 @@ public: , const LLEmojiDescriptor* descr , std::string category) : LLScrollingPanel(panel_params) - , mEmoji(descr->Character) - , mText(LLWString(1, mEmoji)) - , mDescr(descr->getShortCodes()) - , mCategory(category) + , mDescr(descr) + , mText(LLWString(1, descr->Character)) { } @@ -200,16 +153,106 @@ public: virtual void updatePanel(BOOL allow_modify) override {} - llwchar getEmoji() const { return mEmoji; } + const LLEmojiDescriptor* getDescr() const { return mDescr; } + llwchar getEmoji() const { return mDescr->Character; } LLWString getText() const { return mText; } - std::string getDescr() const { return mDescr; } - std::string getCategory() const { return mCategory; } private: - const llwchar mEmoji; + const LLEmojiDescriptor* mDescr; const LLWString mText; - const std::string mDescr; - const std::string mCategory; +}; + +class LLEmojiPreviewPanel : public LLPanel +{ +public: + LLEmojiPreviewPanel() + : LLPanel() + { + } + + void setEmoji(const LLEmojiDescriptor* descr) + { + mDescr = descr; + + if (!mDescr) + return; + + mEmojiText = LLWString(1, descr->Character); + } + + virtual void draw() override + { + LLPanel::draw(); + + if (!mDescr) + return; + + S32 clientHeight = getRect().getHeight(); + S32 clientWidth = getRect().getWidth(); + S32 iconWidth = clientHeight; + + F32 centerX = 0.5f * iconWidth; + F32 centerY = 0.5f * clientHeight; + drawIcon(centerX, centerY, iconWidth); + + static LLColor4 defaultColor(0.75f, 0.75f, 0.75f, 1.0f); + LLColor4 textColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", defaultColor); + S32 max_pixels = clientWidth - iconWidth; + size_t count = mDescr->ShortCodes.size(); + if (count == 1) + { + drawName(mDescr->ShortCodes.front(), iconWidth, centerY, max_pixels, textColor); + } + else if (count > 1) + { + F32 quarterY = 0.5f * centerY; + drawName(mDescr->ShortCodes.front(), iconWidth, centerY + quarterY, max_pixels, textColor); + drawName(*++mDescr->ShortCodes.begin(), iconWidth, quarterY, max_pixels, textColor); + } + } + +protected: + void drawIcon(F32 x, F32 y, S32 max_pixels) + { + LLFontGL::getFontEmojiHuge()->render( + mEmojiText, // 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 + max_pixels, // max_pixels + nullptr, // right_x + false, // use_ellipses + true); // use_color + } + + void drawName(std::string name, F32 x, F32 y, S32 max_pixels, LLColor4& color) + { + LLFontGL::getFontEmoji()->renderUTF8( + name, // wstr + 0, // begin_offset + x, // x + y, // y + color, // color + LLFontGL::LEFT, // halign + LLFontGL::VCENTER, // valign + LLFontGL::NORMAL, // style + LLFontGL::DROP_SHADOW_SOFT, // shadow + -1, // max_chars + max_pixels, // max_pixels + nullptr, // right_x + true, // use_ellipses + false); // use_color + } + +private: + const LLEmojiDescriptor* mDescr { nullptr }; + LLWString mEmojiText; }; LLFloaterEmojiPicker* LLFloaterEmojiPicker::getInstance() @@ -244,19 +287,17 @@ 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); + mPreview = new LLEmojiPreviewPanel(); + mPreview->setVisible(FALSE); + addChild(mPreview); mGroups = getChild("Groups"); mBadge = getChild("Badge"); - mSearch = getChild("Search"); - mSearch->setKeystrokeCallback([this](LLLineEditor*, void*) { onSearchKeystroke(); }, NULL); - mSearch->setFont(LLViewerChat::getChatFont()); - mSearch->setText(sSearchPattern); + mFilter = getChild("Filter"); + mFilter->setKeystrokeCallback([this](LLLineEditor*, void*) { onSearchKeystroke(); }, NULL); + mFilter->setFont(LLViewerChat::getChatFont()); + mFilter->setText(sFilterPattern); mEmojiScroll = getChild("EmojiGridContainer"); mEmojiScroll->setMouseEnterCallback([this](LLUICtrl*, const LLSD&) { onGridMouseEnter(); }); @@ -274,6 +315,16 @@ void LLFloaterEmojiPicker::dirtyRect() { super::dirtyRect(); + if (!mFilter) + return; + + const S32 PADDING = 4; + LLRect rect(PADDING, mFilter->getRect().mTop, getRect().getWidth() - PADDING * 2, PADDING); + if (mPreview->getRect() != rect) + { + mPreview->setRect(rect); + } + if (mEmojiScroll && mEmojiScroll->getRect().getWidth() != mRecentGridWidth) { moveGroups(); @@ -374,7 +425,7 @@ void LLFloaterEmojiPicker::fillEmojis(bool fromResize) mHoveredIcon = nullptr; mEmojiGrid->clearPanels(); - mPreviewEmoji->setLabel(LLUIString()); + mPreview->setEmoji(nullptr); if (mEmojiGrid->getRect().getWidth() != clientWidth) { @@ -395,18 +446,17 @@ void LLFloaterEmojiPicker::fillEmojis(bool fromResize) LLPanel::Params icon_params; LLRect icon_rect(0, iconSize, iconSize, 0); - static const LLColor4 bgcolors[] = + static LLColor4 defaultColor(0.75f, 0.75f, 0.75f, 1.0f); + LLColor4 bgColor = LLUIColorTable::instance().getColor("MenuItemHighlightBgColor", defaultColor); + + auto matchesPattern = [](const LLEmojiDescriptor* descr) -> bool { - 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) + for (const std::string& shortCode : descr->ShortCodes) + if (shortCode.find(sFilterPattern) != std::string::npos) + return true; + return false; }; - static constexpr U32 bgcolorCount = sizeof(bgcolors) / sizeof(*bgcolors); - auto listCategory = [&](std::string category, const std::vector& emojis, int maxRows = 0) { int rowCount = 0; @@ -418,9 +468,10 @@ void LLFloaterEmojiPicker::fillEmojis(bool fromResize) { LLStringUtil::capitalize(category); } + for (const LLEmojiDescriptor* descr : emojis) { - if (sSearchPattern.empty() || matchesPattern(descr)) + if (sFilterPattern.empty() || matchesPattern(descr)) { // Place a category title if needed if (showDivider) @@ -443,8 +494,9 @@ void LLFloaterEmojiPicker::fillEmojis(bool fromResize) LLEmojiGridIcon* icon = new LLEmojiGridIcon(icon_params, descr, mixedFolder ? LLStringUtil::capitalize(descr->Category) : 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->setMouseDownCallback([this](LLUICtrl* ctrl, S32, S32, MASK) { onEmojiMouseDown(ctrl); }); + icon->setMouseUpCallback([this](LLUICtrl* ctrl, S32, S32, MASK) { onEmojiMouseUp(ctrl); }); + icon->setBackgroundColor(bgColor); icon->setBackgroundOpaque(1); icon->setRect(icon_rect); row->mList->addPanel(icon, true); @@ -511,14 +563,6 @@ void LLFloaterEmojiPicker::fillEmojis(bool fromResize) } } -bool LLFloaterEmojiPicker::matchesPattern(const LLEmojiDescriptor* descr) -{ - for (const std::string& shortCode : descr->ShortCodes) - if (shortCode.find(sSearchPattern) != std::string::npos) - return true; - return false; -} - void LLFloaterEmojiPicker::onGroupButtonClick(LLUICtrl* ctrl) { if (LLButton* button = dynamic_cast(ctrl)) @@ -541,7 +585,7 @@ void LLFloaterEmojiPicker::onGroupButtonClick(LLUICtrl* ctrl) rect.mRight = button->getRect().mRight; mBadge->setRect(rect); - mSearch->setFocus(TRUE); + mFilter->setFocus(TRUE); fillEmojis(); } @@ -549,34 +593,22 @@ void LLFloaterEmojiPicker::onGroupButtonClick(LLUICtrl* ctrl) void LLFloaterEmojiPicker::onSearchKeystroke() { - sSearchPattern = mSearch->getText(); + sFilterPattern = mFilter->getText(); fillEmojis(); } -void LLFloaterEmojiPicker::onPreviewEmojiClick() -{ - 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); + mFilter->setVisible(FALSE); + mPreview->setEmoji(nullptr); + mPreview->setVisible(TRUE); } void LLFloaterEmojiPicker::onGridMouseLeave() { - mDescription->setVisible(FALSE); - mDescription->setText(LLStringExplicit(""), LLStyle::Params()); - mSearch->setVisible(TRUE); - mSearch->setFocus(TRUE); + mPreview->setVisible(FALSE); + mFilter->setVisible(TRUE); + mFilter->setFocus(TRUE); } void LLFloaterEmojiPicker::onGroupButtonMouseEnter(LLUICtrl* ctrl) @@ -621,18 +653,29 @@ void LLFloaterEmojiPicker::onEmojiMouseLeave(LLUICtrl* ctrl) } } -void LLFloaterEmojiPicker::onEmojiMouseClick(LLUICtrl* ctrl, MASK mask) +void LLFloaterEmojiPicker::onEmojiMouseDown(LLUICtrl* ctrl) +{ + if (getSoundFlags() & MOUSE_DOWN) + { + make_ui_sound("UISndClick"); + } +} + +void LLFloaterEmojiPicker::onEmojiMouseUp(LLUICtrl* ctrl) { + if (getSoundFlags() & MOUSE_UP) + { + make_ui_sound("UISndClickRelease"); + } + if (mEmojiPickCallback) { if (LLEmojiGridIcon* icon = dynamic_cast(ctrl)) { onEmojiUsed(icon->getEmoji()); - mPreviewEmoji->handleAnyMouseClick(0, 0, 0, EMouseClickType::CLICK_LEFT, TRUE); - mPreviewEmoji->handleAnyMouseClick(0, 0, 0, EMouseClickType::CLICK_LEFT, FALSE); - if (!(mask & 4)) + if (mEmojiPickCallback) { - closeFloater(); + mEmojiPickCallback(icon->getEmoji()); } } } @@ -643,13 +686,7 @@ void LLFloaterEmojiPicker::selectGridIcon(LLUICtrl* ctrl) if (LLEmojiGridIcon* icon = dynamic_cast(ctrl)) { icon->setBackgroundVisible(TRUE); - - LLUIString text; - text.insert(0, icon->getText()); - mPreviewEmoji->setLabel(text); - - std::string descr = icon->getDescr() + "\n" + icon->getCategory(); - mDescription->setText(LLStringExplicit(descr), LLStyle::Params()); + mPreview->setEmoji(icon->getDescr()); } } @@ -658,8 +695,7 @@ void LLFloaterEmojiPicker::unselectGridIcon(LLUICtrl* ctrl) if (LLEmojiGridIcon* icon = dynamic_cast(ctrl)) { icon->setBackgroundVisible(FALSE); - mPreviewEmoji->setLabel(LLUIString()); - mDescription->setText(LLStringExplicit(""), LLStyle::Params()); + mPreview->setEmoji(nullptr); } } @@ -754,7 +790,7 @@ void LLFloaterEmojiPicker::loadState() sSelectedGroupIndex = state[sKeySelectedGroupIndex].asInteger(); - sSearchPattern = state[sKeySearchPattern].asString(); + sFilterPattern = state[sKeyFilterPattern].asString(); // Load and parse sRecentlyUsed std::string recentlyUsed = state[sKeyRecentlyUsed]; @@ -826,9 +862,9 @@ void LLFloaterEmojiPicker::saveState() state[sKeySelectedGroupIndex] = (int)sSelectedGroupIndex; } - if (!sSearchPattern.empty()) + if (!sFilterPattern.empty()) { - state[sKeySearchPattern] = sSearchPattern; + state[sKeyFilterPattern] = sFilterPattern; } if (!sRecentlyUsed.empty()) -- cgit v1.2.3 From 264d9c32d9e04df0ceeaf2a63f6872aad29dd46a Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Fri, 28 Jul 2023 18:42:57 +0200 Subject: SL-20088 EmojiPicker - replace the image on the activation button with an emoji --- indra/newview/llfloateremojipicker.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index 808aca4bf4..c3344fc18a 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -355,10 +355,7 @@ void LLFloaterEmojiPicker::fillGroups() button->setMouseLeaveCallback([this](LLUICtrl* ctrl, const LLSD&) { onGroupButtonMouseLeave(ctrl); }); button->setRect(rect); - - LLUIString text; - text.insert(0, LLWString(1, group.Character)); - button->setLabel(text); + button->setLabel(LLUIString(LLWString(1, group.Character))); if (mGroupButtons.size() == sSelectedGroupIndex) { -- cgit v1.2.3 From 031032fb892560a0f5fa1a996aea77f593f54704 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Tue, 29 Aug 2023 13:12:09 +0200 Subject: SL-20211 EmojiPicker - The search field should be the same as other floaters --- indra/newview/llfloateremojipicker.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index c3344fc18a..af807ba7cf 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -31,9 +31,9 @@ #include "llbutton.h" #include "llcombobox.h" #include "llemojidictionary.h" +#include "llfiltereditor.h" #include "llfloaterreg.h" #include "llkeyboard.h" -#include "lllineeditor.h" #include "llscrollcontainer.h" #include "llscrollingpanellist.h" #include "llscrolllistctrl.h" @@ -294,9 +294,9 @@ BOOL LLFloaterEmojiPicker::postBuild() mGroups = getChild("Groups"); mBadge = getChild("Badge"); - mFilter = getChild("Filter"); - mFilter->setKeystrokeCallback([this](LLLineEditor*, void*) { onSearchKeystroke(); }, NULL); - mFilter->setFont(LLViewerChat::getChatFont()); + mFilter = getChild("Filter"); + mFilter->setKeystrokeCallback([this](LLUICtrl*, const LLSD&) { onFilterChanged(); }); + mFilter->setTextChangedCallback([this](LLUICtrl*, const LLSD&) { onFilterChanged(); }); mFilter->setText(sFilterPattern); mEmojiScroll = getChild("EmojiGridContainer"); @@ -588,7 +588,7 @@ void LLFloaterEmojiPicker::onGroupButtonClick(LLUICtrl* ctrl) } } -void LLFloaterEmojiPicker::onSearchKeystroke() +void LLFloaterEmojiPicker::onFilterChanged() { sFilterPattern = mFilter->getText(); fillEmojis(); -- cgit v1.2.3 From 1e5ac99fd840b7d85d06fd08b27f2c2cb6201b9c Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Tue, 29 Aug 2023 14:23:48 +0200 Subject: SL-20209 Open read access to the recently used emojis --- indra/newview/llfloateremojipicker.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index af807ba7cf..95ea9fc478 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -723,6 +723,14 @@ void LLFloaterEmojiPicker::closeFloater(bool app_quitting) } } +// static +std::list& LLFloaterEmojiPicker::getRecentlyUsed() +{ + loadState(); + return sRecentlyUsed; +} + +// static void LLFloaterEmojiPicker::onEmojiUsed(llwchar emoji) { // Update sRecentlyUsed @@ -762,6 +770,7 @@ void LLFloaterEmojiPicker::onEmojiUsed(llwchar emoji) sFrequentlyUsed.push_back(std::make_pair(emoji, 1)); } +// static void LLFloaterEmojiPicker::loadState() { if (!sStateFileName.empty()) @@ -844,6 +853,7 @@ void LLFloaterEmojiPicker::loadState() } } +// static void LLFloaterEmojiPicker::saveState() { if (sStateFileName.empty()) -- cgit v1.2.3 From 44098db8b3fd96726fec7ec4b68b258a3408dff0 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Wed, 30 Aug 2023 04:35:48 +0200 Subject: SL-20210 Recent Emojis - Remember emojis that were actually sent in the message --- indra/newview/llfloateremojipicker.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index 95ea9fc478..d24f627cdf 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -669,7 +669,6 @@ void LLFloaterEmojiPicker::onEmojiMouseUp(LLUICtrl* ctrl) { if (LLEmojiGridIcon* icon = dynamic_cast(ctrl)) { - onEmojiUsed(icon->getEmoji()); if (mEmojiPickCallback) { mEmojiPickCallback(icon->getEmoji()); -- cgit v1.2.3 From a04f44b7fc76bdaa022642d6e01c72e5996eb989 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Mon, 4 Sep 2023 19:20:43 +0200 Subject: Fix emoji category filter not working correctly --- indra/newview/llfloateremojipicker.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index d24f627cdf..eea9c685b3 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -534,9 +534,10 @@ void LLFloaterEmojiPicker::fillEmojis(bool fromResize) for (const LLEmojiGroup& group : groups) { // List all categories in group - for (const std::string& category : group.Categories) + for (std::string category : group.Categories) { // List all emojis in category + LLStringUtil::toLower(category); const LLEmojiDictionary::cat2descrs_map_t::const_iterator& item = category2Descr.find(category); if (item != category2Descr.end()) { @@ -548,9 +549,10 @@ void LLFloaterEmojiPicker::fillEmojis(bool fromResize) else { // List all categories in the selected group - for (const std::string& category : groups[sSelectedGroupIndex].Categories) + for (std::string category : groups[sSelectedGroupIndex].Categories) { // List all emojis in category + LLStringUtil::toLower(category); const LLEmojiDictionary::cat2descrs_map_t::const_iterator& item = category2Descr.find(category); if (item != category2Descr.end()) { -- cgit v1.2.3 From c30208d598d43779cea7b5de50c44d4b21b16487 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Fri, 8 Sep 2023 01:02:06 +0200 Subject: SL-19951 Organize emoji categories in groups (fix for German) --- indra/newview/llfloateremojipicker.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index eea9c685b3..d24f627cdf 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -534,10 +534,9 @@ void LLFloaterEmojiPicker::fillEmojis(bool fromResize) for (const LLEmojiGroup& group : groups) { // List all categories in group - for (std::string category : group.Categories) + for (const std::string& category : group.Categories) { // List all emojis in category - LLStringUtil::toLower(category); const LLEmojiDictionary::cat2descrs_map_t::const_iterator& item = category2Descr.find(category); if (item != category2Descr.end()) { @@ -549,10 +548,9 @@ void LLFloaterEmojiPicker::fillEmojis(bool fromResize) else { // List all categories in the selected group - for (std::string category : groups[sSelectedGroupIndex].Categories) + for (const std::string& category : groups[sSelectedGroupIndex].Categories) { // List all emojis in category - LLStringUtil::toLower(category); const LLEmojiDictionary::cat2descrs_map_t::const_iterator& item = category2Descr.find(category); if (item != category2Descr.end()) { -- cgit v1.2.3 From 29a3295c82fb65143f8eea7e27c61436a19b4272 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Fri, 8 Sep 2023 12:19:21 +0200 Subject: SL-19951 EmojiPicker - preserve original character case for German categories --- indra/newview/llfloateremojipicker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index d24f627cdf..1169100336 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -461,7 +461,7 @@ void LLFloaterEmojiPicker::fillEmojis(bool fromResize) bool showDivider = true; bool mixedFolder = maxRows; LLEmojiGridRow* row = nullptr; - if (!mixedFolder) + if (!mixedFolder && !isupper(category.front())) { LLStringUtil::capitalize(category); } -- cgit v1.2.3 From 771d409b08eb6073f87aee25a95c242d8178d705 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Sat, 16 Sep 2023 16:09:25 +0200 Subject: SL-20211 EmojiPicker - Move the filter field to the top of the floater --- indra/newview/llfloateremojipicker.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index 1169100336..1608a4f19c 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -193,7 +193,7 @@ public: F32 centerX = 0.5f * iconWidth; F32 centerY = 0.5f * clientHeight; - drawIcon(centerX, centerY, iconWidth); + drawIcon(centerX, centerY - 1, iconWidth); static LLColor4 defaultColor(0.75f, 0.75f, 0.75f, 1.0f); LLColor4 textColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", defaultColor); @@ -291,6 +291,8 @@ BOOL LLFloaterEmojiPicker::postBuild() mPreview->setVisible(FALSE); addChild(mPreview); + mDummy = getChild("Dummy"); + mGroups = getChild("Groups"); mBadge = getChild("Badge"); @@ -318,8 +320,9 @@ void LLFloaterEmojiPicker::dirtyRect() if (!mFilter) return; - const S32 PADDING = 4; - LLRect rect(PADDING, mFilter->getRect().mTop, getRect().getWidth() - PADDING * 2, PADDING); + const S32 HPADDING = 4; + const S32 VOFFSET = 12; + LLRect rect(HPADDING, mDummy->getRect().mTop + 6, getRect().getWidth() - HPADDING, VOFFSET); if (mPreview->getRect() != rect) { mPreview->setRect(rect); @@ -596,7 +599,7 @@ void LLFloaterEmojiPicker::onFilterChanged() void LLFloaterEmojiPicker::onGridMouseEnter() { - mFilter->setVisible(FALSE); + mDummy->setVisible(FALSE); mPreview->setEmoji(nullptr); mPreview->setVisible(TRUE); } @@ -604,8 +607,7 @@ void LLFloaterEmojiPicker::onGridMouseEnter() void LLFloaterEmojiPicker::onGridMouseLeave() { mPreview->setVisible(FALSE); - mFilter->setVisible(TRUE); - mFilter->setFocus(TRUE); + mDummy->setVisible(TRUE); } void LLFloaterEmojiPicker::onGroupButtonMouseEnter(LLUICtrl* ctrl) -- cgit v1.2.3 From c40f63911c1fad5ea5a18894f530892ddb87c8ea Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Fri, 22 Sep 2023 01:19:42 +0200 Subject: SL-20294 The row of recent emojis displays the recently sent emoji only after clicking the collapsing smiley button --- indra/newview/llfloateremojipicker.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index 1608a4f19c..10013c907d 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -771,6 +771,18 @@ void LLFloaterEmojiPicker::onEmojiUsed(llwchar emoji) sFrequentlyUsed.push_back(std::make_pair(emoji, 1)); } +// static +void LLFloaterEmojiPicker::onRecentlyUsedChanged() +{ + if (sSelectedGroupIndex) + return; + + if (LLFloaterEmojiPicker* picker = getInstance()) + { + picker->fillEmojis(); + } +} + // static void LLFloaterEmojiPicker::loadState() { -- cgit v1.2.3 From 716b9af35d0dbc3ba8048a1bcca2c8c8f703bed0 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Tue, 7 Nov 2023 23:13:00 +0100 Subject: SL-20356 EmojiPicker - Implement arrow keys navigation --- indra/newview/llfloateremojipicker.cpp | 194 ++++++++++++++++++++++++++++++++- 1 file changed, 189 insertions(+), 5 deletions(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index 10013c907d..45c33728e5 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -399,6 +399,13 @@ void LLFloaterEmojiPicker::moveGroups() mBadge->setRect(rect); } +void LLFloaterEmojiPicker::showPreview(bool show) +{ + mPreview->setEmoji(nullptr); + mDummy->setVisible(show ? FALSE : TRUE); + mPreview->setVisible(show ? TRUE : FALSE); +} + void LLFloaterEmojiPicker::fillEmojis(bool fromResize) { mRecentGridWidth = mEmojiScroll->getRect().getWidth(); @@ -423,6 +430,9 @@ void LLFloaterEmojiPicker::fillEmojis(bool fromResize) mRecentMaxIcons = maxIcons; + mFocusedIconRow = 0; + mFocusedIconCol = 0; + mFocusedIcon = nullptr; mHoveredIcon = nullptr; mEmojiGrid->clearPanels(); mPreview->setEmoji(nullptr); @@ -599,15 +609,17 @@ void LLFloaterEmojiPicker::onFilterChanged() void LLFloaterEmojiPicker::onGridMouseEnter() { - mDummy->setVisible(FALSE); - mPreview->setEmoji(nullptr); - mPreview->setVisible(TRUE); + LLFocusableElement* focus = gFocusMgr.getKeyboardFocus(); + if (focus == mEmojiGrid) + { + exitArrowMode(); + } + showPreview(true); } void LLFloaterEmojiPicker::onGridMouseLeave() { - mPreview->setVisible(FALSE); - mDummy->setVisible(TRUE); + showPreview(false); } void LLFloaterEmojiPicker::onGroupButtonMouseEnter(LLUICtrl* ctrl) @@ -630,6 +642,13 @@ void LLFloaterEmojiPicker::onEmojiMouseEnter(LLUICtrl* ctrl) { if (ctrl) { + LLFocusableElement* focus = gFocusMgr.getKeyboardFocus(); + if (focus == mEmojiGrid) + { + exitArrowMode(); + showPreview(true); + } + if (mHoveredIcon && mHoveredIcon != ctrl) { unselectGridIcon(mHoveredIcon); @@ -679,6 +698,113 @@ void LLFloaterEmojiPicker::onEmojiMouseUp(LLUICtrl* ctrl) } } +bool LLFloaterEmojiPicker::enterArrowMode() +{ + S32 rowCount = mEmojiGrid->getPanelList().size(); + if (rowCount) + { + mFocusedIconRow = -1; + mFocusedIconCol = 0; + if (moveFocusedIconDown()) + { + showPreview(true); + mEmojiScroll->goToTop(); + mEmojiGrid->setFocus(TRUE); + return true; + } + } + return false; +} + +void LLFloaterEmojiPicker::exitArrowMode() +{ + if (mFocusedIcon) + { + unselectGridIcon(mFocusedIcon); + mFocusedIcon = nullptr; + } + + showPreview(false); + mEmojiScroll->goToTop(); + mFocusedIconRow = mFocusedIconCol = 0; + mFilter->setFocus(TRUE); +} + +void LLFloaterEmojiPicker::selectFocusedIcon() +{ + if (mHoveredIcon) + { + unselectGridIcon(mHoveredIcon); + } + + if (mFocusedIcon) + { + unselectGridIcon(mFocusedIcon); + } + + // Both mFocusedIconRow and mFocusedIconCol should be already verified + LLEmojiGridRow* row = (LLEmojiGridRow*)mEmojiGrid->getPanelList()[mFocusedIconRow]; + mFocusedIcon = row->mList->getPanelList()[mFocusedIconCol]; + selectGridIcon(mFocusedIcon); +} + +bool LLFloaterEmojiPicker::moveFocusedIconUp() +{ + for (S32 i = mFocusedIconRow - 1; i >= 0; --i) + { + LLScrollingPanel* panel = mEmojiGrid->getPanelList()[i]; + LLEmojiGridRow* row = dynamic_cast(panel); + if (row && row->mList->getPanelList().size() > mFocusedIconCol) + { + mEmojiScroll->scrollToShowRect(row->getBoundingRect()); + mFocusedIconRow = i; + selectFocusedIcon(); + return true; + } + } + return false; +} + +bool LLFloaterEmojiPicker::moveFocusedIconDown() +{ + S32 rowCount = mEmojiGrid->getPanelList().size(); + for (S32 i = mFocusedIconRow + 1; i < rowCount; ++i) + { + LLScrollingPanel* panel = mEmojiGrid->getPanelList()[i]; + LLEmojiGridRow* row = dynamic_cast(panel); + if (row && row->mList->getPanelList().size() > mFocusedIconCol) + { + mEmojiScroll->scrollToShowRect(row->getBoundingRect()); + mFocusedIconRow = i; + selectFocusedIcon(); + return true; + } + } + return false; +} + +bool LLFloaterEmojiPicker::moveFocusedIconLeft() +{ + if (mFocusedIconCol <= 0) + return false; + + mFocusedIconCol--; + selectFocusedIcon(); + return true; +} + +bool LLFloaterEmojiPicker::moveFocusedIconRight() +{ + LLEmojiGridRow* row = (LLEmojiGridRow*)mEmojiGrid->getPanelList()[mFocusedIconRow]; + S32 colCount = row->mList->getPanelList().size(); + if (mFocusedIconCol >= colCount - 1) + return false; + + mFocusedIconCol++; + selectFocusedIcon(); + return true; +} + void LLFloaterEmojiPicker::selectGridIcon(LLUICtrl* ctrl) { if (LLEmojiGridIcon* icon = dynamic_cast(ctrl)) @@ -697,6 +823,64 @@ void LLFloaterEmojiPicker::unselectGridIcon(LLUICtrl* ctrl) } } +// virtual +BOOL LLFloaterEmojiPicker::handleKey(KEY key, MASK mask, BOOL called_from_parent) +{ + if (mask == MASK_NONE) + { + LLFocusableElement* focus = gFocusMgr.getKeyboardFocus(); + if (focus == mEmojiGrid) + { + if (key == KEY_RETURN) + { + if (mFocusedIcon) + { + onEmojiMouseDown(mFocusedIcon); + onEmojiMouseUp(mFocusedIcon); + closeFloater(); + return TRUE; + } + } + else if (key == KEY_TAB) + { + exitArrowMode(); + return TRUE; + } + else if (key == KEY_UP) + { + if (!moveFocusedIconUp()) + exitArrowMode(); + return TRUE; + } + else if (key == KEY_DOWN) + { + if (moveFocusedIconDown()) + return TRUE; + } + else if (key == KEY_LEFT) + { + if (moveFocusedIconLeft()) + return TRUE; + } + else if (key == KEY_RIGHT) + { + if (moveFocusedIconRight()) + return TRUE; + } + } + else // if (focus != mEmojiGrid) + { + if (key == KEY_DOWN) + { + if (enterArrowMode()) + return TRUE; + } + } + } + + return super::handleKey(key, mask, called_from_parent); +} + // virtual BOOL LLFloaterEmojiPicker::handleKeyHere(KEY key, MASK mask) { -- cgit v1.2.3 From bee1a78f5a2e6c455dfe7cb940ee26df77e51cff Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Wed, 8 Nov 2023 11:56:13 +0100 Subject: SL-20415 Activating an already active category button should move focus to search field --- indra/newview/llfloateremojipicker.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index 45c33728e5..df973a15b5 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -577,6 +577,8 @@ void LLFloaterEmojiPicker::onGroupButtonClick(LLUICtrl* ctrl) { if (LLButton* button = dynamic_cast(ctrl)) { + mFilter->setFocus(TRUE); + if (button == mGroupButtons[sSelectedGroupIndex] || button->getToggleState()) return; @@ -595,8 +597,6 @@ void LLFloaterEmojiPicker::onGroupButtonClick(LLUICtrl* ctrl) rect.mRight = button->getRect().mRight; mBadge->setRect(rect); - mFilter->setFocus(TRUE); - fillEmojis(); } } -- cgit v1.2.3 From 1e7643eea5883db5b773db99b37440eb06b63e3e Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Thu, 9 Nov 2023 17:51:32 +0100 Subject: SL-20438 Emoji picker will dock with edge of screen if the Conversations floater is dragged there --- indra/newview/llfloateremojipicker.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index df973a15b5..a9bc0fd6bb 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -279,7 +279,7 @@ void LLFloaterEmojiPicker::show(pick_callback_t pick_callback, close_callback_t } LLFloaterEmojiPicker::LLFloaterEmojiPicker(const LLSD& key) -: LLFloater(key) +: super(key) { loadState(); } @@ -894,14 +894,14 @@ BOOL LLFloaterEmojiPicker::handleKeyHere(KEY key, MASK mask) } } - return LLFloater::handleKeyHere(key, mask); + return super::handleKeyHere(key, mask); } // virtual void LLFloaterEmojiPicker::closeFloater(bool app_quitting) { saveState(); - LLFloater::closeFloater(app_quitting); + super::closeFloater(app_quitting); if (mFloaterCloseCallback) { mFloaterCloseCallback(); -- cgit v1.2.3 From 65b501c42973e3ffdbf37e70ed78243bab588039 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Tue, 16 Jan 2024 02:12:34 +0100 Subject: SL-20751 Information about frequently used and recently used emojis is available to all users --- indra/newview/llfloateremojipicker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index a9bc0fd6bb..abc5165f1e 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -973,7 +973,7 @@ void LLFloaterEmojiPicker::loadState() if (!sStateFileName.empty()) return; // Already loaded - sStateFileName = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "emoji_floater_state.xml"); + sStateFileName = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "emoji_floater_state.xml"); llifstream file; file.open(sStateFileName.c_str()); -- cgit v1.2.3 From ae91ae43a51c58cc496f3947921fbf886c6be86e Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Mon, 15 Jan 2024 23:20:24 +0100 Subject: SL-20795 Part of previously typed emojis disappear in the 'Save settings as a preset...' option of the 'Preferences' floater --- indra/newview/llfloateremojipicker.cpp | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index abc5165f1e..ecfc4e7b41 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -102,11 +102,7 @@ public: 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 + mText.size()); // max_chars } virtual void updatePanel(BOOL allow_modify) override {} @@ -144,11 +140,7 @@ public: 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 + 1); // max_chars } virtual void updatePanel(BOOL allow_modify) override {} @@ -225,10 +217,7 @@ protected: LLFontGL::NORMAL, // style LLFontGL::DROP_SHADOW_SOFT, // shadow 1, // max_chars - max_pixels, // max_pixels - nullptr, // right_x - false, // use_ellipses - true); // use_color + max_pixels); // max_pixels } void drawName(std::string name, F32 x, F32 y, S32 max_pixels, LLColor4& color) @@ -244,10 +233,7 @@ protected: LLFontGL::NORMAL, // style LLFontGL::DROP_SHADOW_SOFT, // shadow -1, // max_chars - max_pixels, // max_pixels - nullptr, // right_x - true, // use_ellipses - false); // use_color + max_pixels); // max_pixels } private: -- cgit v1.2.3 From f6ceafee5bb26cf86d64959cabf68deefaf791a8 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Mon, 22 Jan 2024 06:40:41 +0100 Subject: SL-20416 Avoid of taking focus by EmojiPicker --- indra/newview/llfloateremojipicker.cpp | 1066 +++++++++++++++++++------------- 1 file changed, 633 insertions(+), 433 deletions(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index ecfc4e7b41..2380fd357b 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -31,7 +31,7 @@ #include "llbutton.h" #include "llcombobox.h" #include "llemojidictionary.h" -#include "llfiltereditor.h" +#include "llemojihelper.h" #include "llfloaterreg.h" #include "llkeyboard.h" #include "llscrollcontainer.h" @@ -46,16 +46,25 @@ namespace { // The following variables and constants are used for storing the floater state // between different lifecycles of the floater and different sissions of the viewer +// Floater constants +static const S32 ALL_EMOJIS_GROUP_INDEX = -2; +// https://www.compart.com/en/unicode/U+1F50D +static const S32 ALL_EMOJIS_IMAGE_INDEX = 0x1F50D; +static const S32 USED_EMOJIS_GROUP_INDEX = -1; +// https://www.compart.com/en/unicode/U+23F2 +static const S32 USED_EMOJIS_IMAGE_INDEX = 0x23F2; +// https://www.compart.com/en/unicode/U+1F6D1 +static const S32 EMPTY_LIST_IMAGE_INDEX = 0x1F6D1; +// The following categories should follow the required alphabetic order +static const std::string RECENTLY_USED_CATEGORY = "1 recently used"; +static const std::string FREQUENTLY_USED_CATEGORY = "2 frequently used"; + // Floater state related variables -static U32 sSelectedGroupIndex = 0; -static std::string sFilterPattern; static std::list sRecentlyUsed; static std::list> sFrequentlyUsed; // State file related values static std::string sStateFileName; -static const std::string sKeySelectedGroupIndex("SelectedGroupIndex"); -static const std::string sKeyFilterPattern("FilterPattern"); static const std::string sKeyRecentlyUsed("RecentlyUsed"); static const std::string sKeyFrequentlyUsed("FrequentlyUsed"); } @@ -116,11 +125,10 @@ class LLEmojiGridIcon : public LLScrollingPanel public: LLEmojiGridIcon( const LLPanel::Params& panel_params - , const LLEmojiDescriptor* descr - , std::string category) + , const LLEmojiSearchResult& emoji) : LLScrollingPanel(panel_params) - , mDescr(descr) - , mText(LLWString(1, descr->Character)) + , mData(emoji) + , mText(LLWString(1, emoji.Character)) { } @@ -145,12 +153,11 @@ public: virtual void updatePanel(BOOL allow_modify) override {} - const LLEmojiDescriptor* getDescr() const { return mDescr; } - llwchar getEmoji() const { return mDescr->Character; } + const LLEmojiSearchResult& getData() const { return mData; } LLWString getText() const { return mText; } private: - const LLEmojiDescriptor* mDescr; + const LLEmojiSearchResult mData; const LLWString mText; }; @@ -162,23 +169,31 @@ public: { } - void setEmoji(const LLEmojiDescriptor* descr) + void setIcon(const LLEmojiGridIcon* icon) { - mDescr = descr; - - if (!mDescr) - return; + if (icon) + { + setData(icon->getData().Character, icon->getData().String, icon->getData().Begin, icon->getData().End); + } + else + { + setData(0, LLStringUtil::null, 0, 0); + } + } - mEmojiText = LLWString(1, descr->Character); + void setData(llwchar emoji, std::string title, size_t begin, size_t end) + { + mWStr = LLWString(1, emoji); + mEmoji = emoji; + mTitle = title; + mBegin = begin; + mEnd = end; } virtual void draw() override { LLPanel::draw(); - if (!mDescr) - return; - S32 clientHeight = getRect().getHeight(); S32 clientWidth = getRect().getWidth(); S32 iconWidth = clientHeight; @@ -190,24 +205,14 @@ public: static LLColor4 defaultColor(0.75f, 0.75f, 0.75f, 1.0f); LLColor4 textColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", defaultColor); S32 max_pixels = clientWidth - iconWidth; - size_t count = mDescr->ShortCodes.size(); - if (count == 1) - { - drawName(mDescr->ShortCodes.front(), iconWidth, centerY, max_pixels, textColor); - } - else if (count > 1) - { - F32 quarterY = 0.5f * centerY; - drawName(mDescr->ShortCodes.front(), iconWidth, centerY + quarterY, max_pixels, textColor); - drawName(*++mDescr->ShortCodes.begin(), iconWidth, quarterY, max_pixels, textColor); - } + drawName(iconWidth, centerY, max_pixels, textColor); } protected: void drawIcon(F32 x, F32 y, S32 max_pixels) { LLFontGL::getFontEmojiHuge()->render( - mEmojiText, // wstr + mWStr, // wstr 0, // begin_offset x, // x y, // y @@ -220,90 +225,118 @@ protected: max_pixels); // max_pixels } - void drawName(std::string name, F32 x, F32 y, S32 max_pixels, LLColor4& color) + void drawName(F32 x, F32 y, S32 max_pixels, LLColor4& color) { - LLFontGL::getFontEmoji()->renderUTF8( - name, // wstr - 0, // begin_offset - x, // x - y, // y - color, // color - LLFontGL::LEFT, // halign - LLFontGL::VCENTER, // valign - LLFontGL::NORMAL, // style - LLFontGL::DROP_SHADOW_SOFT, // shadow - -1, // max_chars - max_pixels); // max_pixels + F32 x0 = x; + F32 x1 = max_pixels; + LLFontGL* font = LLFontGL::getFontEmoji(); + if (mBegin) + { + std::string text = mTitle.substr(0, mBegin); + font->renderUTF8( + text, // text + 0, // begin_offset + x0, // x + y, // y + color, // color + LLFontGL::LEFT, // halign + LLFontGL::VCENTER, // valign + LLFontGL::NORMAL, // style + LLFontGL::DROP_SHADOW_SOFT, // shadow + text.size(), // max_chars + x1); // max_pixels + F32 dx = font->getWidthF32(text); + x0 += dx; + x1 -= dx; + } + if (x1 > 0 && mEnd > mBegin) + { + std::string text = mTitle.substr(mBegin, mEnd - mBegin); + font->renderUTF8( + text, // text + 0, // begin_offset + x0, // x + y, // y + LLColor4::yellow6, // color + LLFontGL::LEFT, // halign + LLFontGL::VCENTER, // valign + LLFontGL::NORMAL, // style + LLFontGL::DROP_SHADOW_SOFT, // shadow + text.size(), // max_chars + x1); // max_pixels + F32 dx = font->getWidthF32(text); + x0 += dx; + x1 -= dx; + } + if (x1 > 0 && mEnd < mTitle.size()) + { + std::string text = mEnd ? mTitle.substr(mEnd) : mTitle; + font->renderUTF8( + text, // text + 0, // begin_offset + x0, // x + y, // y + color, // color + LLFontGL::LEFT, // halign + LLFontGL::VCENTER, // valign + LLFontGL::NORMAL, // style + LLFontGL::DROP_SHADOW_SOFT, // shadow + text.size(), // max_chars + x1); // max_pixels + } } private: - const LLEmojiDescriptor* mDescr { nullptr }; - LLWString mEmojiText; + llwchar mEmoji; + LLWString mWStr; + std::string mTitle; + size_t mBegin; + size_t mEnd; }; -LLFloaterEmojiPicker* LLFloaterEmojiPicker::getInstance() -{ - 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; -} - -void LLFloaterEmojiPicker::show(pick_callback_t pick_callback, close_callback_t close_callback) -{ - mEmojiPickCallback = pick_callback; - mFloaterCloseCallback = close_callback; - openFloater(mKey); - setFocus(TRUE); -} - LLFloaterEmojiPicker::LLFloaterEmojiPicker(const LLSD& key) : super(key) { + // This floater should hover on top of our dependent (with the dependent having the focus) + setFocusStealsFrontmost(FALSE); + setBackgroundVisible(FALSE); + setAutoFocus(FALSE); + loadState(); } BOOL LLFloaterEmojiPicker::postBuild() { - // Should be initialized first + mGroups = getChild("Groups"); + mBadge = getChild("Badge"); + mEmojiScroll = getChild("EmojiGridContainer"); + mEmojiGrid = getChild("EmojiGrid"); + mDummy = getChild("Dummy"); + mPreview = new LLEmojiPreviewPanel(); mPreview->setVisible(FALSE); addChild(mPreview); - mDummy = getChild("Dummy"); - - mGroups = getChild("Groups"); - mBadge = getChild("Badge"); - - mFilter = getChild("Filter"); - mFilter->setKeystrokeCallback([this](LLUICtrl*, const LLSD&) { onFilterChanged(); }); - mFilter->setTextChangedCallback([this](LLUICtrl*, const LLSD&) { onFilterChanged(); }); - mFilter->setText(sFilterPattern); + return LLFloater::postBuild(); +} - mEmojiScroll = getChild("EmojiGridContainer"); - mEmojiScroll->setMouseEnterCallback([this](LLUICtrl*, const LLSD&) { onGridMouseEnter(); }); - mEmojiScroll->setMouseLeaveCallback([this](LLUICtrl*, const LLSD&) { onGridMouseLeave(); }); +void LLFloaterEmojiPicker::onOpen(const LLSD& key) +{ + mHint = key["hint"].asString(); - mEmojiGrid = getChild("EmojiGrid"); + LLEmojiHelper::instance().setIsHideDisabled(mHint.empty()); + mFilterPattern = mHint; - fillGroups(); - fillEmojis(); + initialize(); - return TRUE; + gFloaterView->adjustToFitScreen(this, FALSE); } void LLFloaterEmojiPicker::dirtyRect() { super::dirtyRect(); - if (!mFilter) + if (!mPreview) return; const S32 HPADDING = 4; @@ -314,20 +347,81 @@ void LLFloaterEmojiPicker::dirtyRect() mPreview->setRect(rect); } - if (mEmojiScroll && mEmojiScroll->getRect().getWidth() != mRecentGridWidth) + if (mEmojiScroll && mEmojiGrid) { - moveGroups(); - fillEmojis(true); + S32 outer_width = mEmojiScroll->getRect().getWidth(); + S32 inner_width = mEmojiGrid->getRect().getWidth(); + if (outer_width != inner_width) + { + resizeGroupButtons(); + fillEmojis(true); + } } } -LLFloaterEmojiPicker::~LLFloaterEmojiPicker() +void LLFloaterEmojiPicker::initialize() { - gFocusMgr.releaseFocusIfNeeded( this ); + S32 groupIndex = mSelectedGroupIndex && mSelectedGroupIndex <= mFilteredEmojiGroups.size() ? + mFilteredEmojiGroups[mSelectedGroupIndex - 1] : ALL_EMOJIS_GROUP_INDEX; + + fillGroups(); + + if (mFilteredEmojis.empty()) + { + if (!mHint.empty()) + { + hideFloater(); + return; + } + + mGroups->setVisible(FALSE); + mFocusedIconRow = -1; + mFocusedIconCol = -1; + mFocusedIcon = nullptr; + mHoveredIcon = nullptr; + mEmojiScroll->goToTop(); + mEmojiGrid->clearPanels(); + + if (mFilterPattern.empty()) + { + showPreview(false); + } + else + { + const std::string prompt("No emoji found for "); + std::string title(prompt + '"' + mFilterPattern.substr(1) + '"'); + mPreview->setData(EMPTY_LIST_IMAGE_INDEX, title, prompt.size() + 1, title.size() - 1); + showPreview(true); + } + return; + } + + mGroups->setVisible(TRUE); + mPreview->setIcon(nullptr); + showPreview(true); + + mSelectedGroupIndex = groupIndex == ALL_EMOJIS_GROUP_INDEX ? 0 : + (1 + std::distance(mFilteredEmojiGroups.begin(), + std::find(mFilteredEmojiGroups.begin(), mFilteredEmojiGroups.end(), groupIndex))) % + (1 + mFilteredEmojiGroups.size()); + + mGroupButtons[mSelectedGroupIndex]->setToggleState(TRUE); + mGroupButtons[mSelectedGroupIndex]->setUseFontColor(TRUE); + + fillEmojis(); } void LLFloaterEmojiPicker::fillGroups() { + // Do not use deleteAllChildren() because mBadge shouldn't be removed + for (LLButton* button : mGroupButtons) + { + mGroups->removeChild(button); + } + mFilteredEmojiGroups.clear(); + mFilteredEmojis.clear(); + mGroupButtons.clear(); + LLButton::Params params; params.font = LLFontGL::getFontEmoji(); @@ -335,277 +429,426 @@ void LLFloaterEmojiPicker::fillGroups() rect.mTop = mGroups->getRect().getHeight(); rect.mBottom = mBadge->getRect().getHeight(); + // Create button for "All categories" + createGroupButton(params, rect, ALL_EMOJIS_IMAGE_INDEX); + + // Create group and button for "Recently used" and/or "Frequently used" + if (!sRecentlyUsed.empty() || !sFrequentlyUsed.empty()) + { + std::map> cats; + fillCategoryRecentlyUsed(cats); + fillCategoryFrequentlyUsed(cats); + + if (!cats.empty()) + { + mFilteredEmojiGroups.push_back(USED_EMOJIS_GROUP_INDEX); + mFilteredEmojis.emplace_back(cats); + createGroupButton(params, rect, USED_EMOJIS_IMAGE_INDEX); + } + } + const std::vector& groups = LLEmojiDictionary::instance().getGroups(); - for (const LLEmojiGroup& group : groups) + + // List all categories in the dictionary + for (U32 i = 0; i < groups.size(); ++i) { - 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); }); + std::map> cats; - button->setRect(rect); - button->setLabel(LLUIString(LLWString(1, group.Character))); + fillGroupEmojis(cats, i); - if (mGroupButtons.size() == sSelectedGroupIndex) + if (!cats.empty()) { - button->setToggleState(TRUE); - button->setUseFontColor(TRUE); + mFilteredEmojiGroups.push_back(i); + mFilteredEmojis.emplace_back(cats); + createGroupButton(params, rect, groups[i].Character); } + } + + resizeGroupButtons(); +} + +void LLFloaterEmojiPicker::fillCategoryRecentlyUsed(std::map>& cats) +{ + if (sRecentlyUsed.empty()) + return; - mGroupButtons.push_back(button); - mGroups->addChild(button); + std::vector emojis; + + // In case of empty mFilterPattern we'd use sRecentlyUsed directly + if (!mFilterPattern.empty()) + { + // List all emojis in "Recently used" + const LLEmojiDictionary::emoji2descr_map_t& emoji2descr = LLEmojiDictionary::instance().getEmoji2Descr(); + std::size_t begin, end; + for (llwchar emoji : sRecentlyUsed) + { + auto e2d = emoji2descr.find(emoji); + if (e2d != emoji2descr.end() && !e2d->second->ShortCodes.empty()) + { + const std::string shortcode(e2d->second->ShortCodes.front()); + if (LLEmojiDictionary::searchInShortCode(begin, end, shortcode, mFilterPattern)) + { + emojis.emplace_back(emoji, shortcode, begin, end); + } + } + } + if (emojis.empty()) + return; } - moveGroups(); + cats.emplace(std::make_pair(RECENTLY_USED_CATEGORY, emojis)); } -void LLFloaterEmojiPicker::moveGroups() +void LLFloaterEmojiPicker::fillCategoryFrequentlyUsed(std::map>& cats) { - const std::vector& groups = LLEmojiDictionary::instance().getGroups(); - if (groups.empty()) + if (sFrequentlyUsed.empty()) return; - int badgeWidth = mGroups->getRect().getWidth() / groups.size(); - if (badgeWidth == mRecentBadgeWidth) + std::vector emojis; + + // In case of empty mFilterPattern we'd use sFrequentlyUsed directly + if (!mFilterPattern.empty()) + { + // List all emojis in "Frequently used" + const LLEmojiDictionary::emoji2descr_map_t& emoji2descr = LLEmojiDictionary::instance().getEmoji2Descr(); + std::size_t begin, end; + for (const auto& emoji : sFrequentlyUsed) + { + auto e2d = emoji2descr.find(emoji.first); + if (e2d != emoji2descr.end() && !e2d->second->ShortCodes.empty()) + { + const std::string shortcode(e2d->second->ShortCodes.front()); + if (LLEmojiDictionary::searchInShortCode(begin, end, shortcode, mFilterPattern)) + { + emojis.emplace_back(emoji.first, shortcode, begin, end); + } + } + } + if (emojis.empty()) + return; + } + + cats.emplace(std::make_pair(FREQUENTLY_USED_CATEGORY, emojis)); +} + +void LLFloaterEmojiPicker::fillGroupEmojis(std::map>& cats, U32 index) +{ + const std::vector& groups = LLEmojiDictionary::instance().getGroups(); + const LLEmojiDictionary::cat2descrs_map_t& category2Descr = LLEmojiDictionary::instance().getCategory2Descrs(); + + for (const std::string& category : groups[index].Categories) + { + const LLEmojiDictionary::cat2descrs_map_t::const_iterator& c2d = category2Descr.find(category); + if (c2d == category2Descr.end()) + continue; + + std::vector emojis; + + // In case of empty mFilterPattern we'd use category2Descr directly + if (!mFilterPattern.empty()) + { + // List all emojis in category + std::size_t begin, end; + for (const LLEmojiDescriptor* descr : c2d->second) + { + if (!descr->ShortCodes.empty()) + { + const std::string shortcode(descr->ShortCodes.front()); + if (LLEmojiDictionary::searchInShortCode(begin, end, shortcode, mFilterPattern)) + { + emojis.emplace_back(descr->Character, shortcode, begin, end); + } + } + } + if (emojis.empty()) + continue; + } + + cats.emplace(std::make_pair(category, emojis)); + } +} + +void LLFloaterEmojiPicker::createGroupButton(LLButton::Params& params, const LLRect& rect, llwchar emoji) +{ + 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); + button->setTabStop(FALSE); + button->setLabel(LLUIString(LLWString(1, emoji))); + button->setUseFontColor(FALSE); + + mGroupButtons.push_back(button); + mGroups->addChild(button); +} + +void LLFloaterEmojiPicker::resizeGroupButtons() +{ + U32 groupCount = (U32)mGroupButtons.size(); + if (!groupCount) return; - mRecentBadgeWidth = badgeWidth; + S32 totalWidth = mGroups->getRect().getWidth(); + S32 badgeWidth = totalWidth / groupCount; + S32 leftOffset = (totalWidth - badgeWidth * groupCount) / 2; - for (int i = 0; i < mGroupButtons.size(); ++i) + for (U32 i = 0; i < groupCount; ++i) { LLRect rect = mGroupButtons[i]->getRect(); - rect.mLeft = badgeWidth * i; + rect.mLeft = leftOffset + badgeWidth * i; rect.mRight = rect.mLeft + badgeWidth; mGroupButtons[i]->setRect(rect); } LLRect rect = mBadge->getRect(); - rect.mLeft = badgeWidth * sSelectedGroupIndex; + rect.mLeft = leftOffset + badgeWidth * mSelectedGroupIndex; rect.mRight = rect.mLeft + badgeWidth; mBadge->setRect(rect); } -void LLFloaterEmojiPicker::showPreview(bool show) +void LLFloaterEmojiPicker::selectEmojiGroup(U32 index) { - mPreview->setEmoji(nullptr); - mDummy->setVisible(show ? FALSE : TRUE); - mPreview->setVisible(show ? TRUE : FALSE); + if (index == mSelectedGroupIndex || index >= mGroupButtons.size()) + return; + + if (mSelectedGroupIndex < mGroupButtons.size()) + { + mGroupButtons[mSelectedGroupIndex]->setUseFontColor(FALSE); + mGroupButtons[mSelectedGroupIndex]->setToggleState(FALSE); + } + + mSelectedGroupIndex = index; + mGroupButtons[mSelectedGroupIndex]->setToggleState(TRUE); + mGroupButtons[mSelectedGroupIndex]->setUseFontColor(TRUE); + + LLButton* button = mGroupButtons[mSelectedGroupIndex]; + LLRect rect = mBadge->getRect(); + rect.mLeft = button->getRect().mLeft; + rect.mRight = button->getRect().mRight; + mBadge->setRect(rect); + + fillEmojis(); } void LLFloaterEmojiPicker::fillEmojis(bool fromResize) { - mRecentGridWidth = mEmojiScroll->getRect().getWidth(); - - S32 scrollbarSize = mEmojiScroll->getSize(); - if (scrollbarSize < 0) + S32 scrollbar_size = mEmojiScroll->getSize(); + if (scrollbar_size < 0) { static LLUICachedControl scrollbar_size_control("UIScrollbarSize", 0); - scrollbarSize = scrollbar_size_control; + scrollbar_size = 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)); + const S32 scroll_width = mEmojiScroll->getRect().getWidth(); + const S32 client_width = scroll_width - scrollbar_size - mEmojiScroll->getBorderWidth() * 2; + const S32 grid_padding = mEmojiGrid->getPadding(); + const S32 icon_spacing = mEmojiGrid->getSpacing(); + const S32 row_width = client_width - grid_padding * 2; + const S32 icon_size = 28; // icon width and height + const S32 max_icons = llmax(1, (row_width + icon_spacing) / (icon_size + icon_spacing)); // Optimization: don't rearrange for different widths with the same maxIcons - if (fromResize && (maxIcons == mRecentMaxIcons)) + if (fromResize && (max_icons == mRecentMaxIcons)) return; - mRecentMaxIcons = maxIcons; + mRecentMaxIcons = max_icons; mFocusedIconRow = 0; mFocusedIconCol = 0; mFocusedIcon = nullptr; mHoveredIcon = nullptr; + mEmojiScroll->goToTop(); mEmojiGrid->clearPanels(); - mPreview->setEmoji(nullptr); + mPreview->setIcon(nullptr); - if (mEmojiGrid->getRect().getWidth() != clientWidth) + if (mEmojiGrid->getRect().getWidth() != client_width) { LLRect rect = mEmojiGrid->getRect(); - rect.mRight = rect.mLeft + clientWidth; + rect.mRight = rect.mLeft + client_width; mEmojiGrid->setRect(rect); } LLPanel::Params row_panel_params; - row_panel_params.rect = LLRect(0, iconSize, rowWidth, 0); + row_panel_params.rect = LLRect(0, icon_size, row_width, 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; + row_list_params.spacing = icon_spacing; LLPanel::Params icon_params; - LLRect icon_rect(0, iconSize, iconSize, 0); + LLRect icon_rect(0, icon_size, icon_size, 0); - static LLColor4 defaultColor(0.75f, 0.75f, 0.75f, 1.0f); - LLColor4 bgColor = LLUIColorTable::instance().getColor("MenuItemHighlightBgColor", defaultColor); + static LLColor4 default_color(0.75f, 0.75f, 0.75f, 1.0f); + LLColor4 bg_color = LLUIColorTable::instance().getColor("MenuItemHighlightBgColor", default_color); - auto matchesPattern = [](const LLEmojiDescriptor* descr) -> bool + if (!mSelectedGroupIndex) { - for (const std::string& shortCode : descr->ShortCodes) - if (shortCode.find(sFilterPattern) != std::string::npos) - return true; - return false; - }; - - auto listCategory = [&](std::string category, const std::vector& emojis, int maxRows = 0) + // List all groups + for (const auto& group : mFilteredEmojis) + { + // List all categories in the group + for (const auto& category : group) + { + // List all emojis in the category + fillEmojisCategory(category.second, category.first, row_panel_params, + row_list_params, icon_params, icon_rect, max_icons, bg_color); + } + } + } + else { - int rowCount = 0; - int iconIndex = 0; - bool showDivider = true; - bool mixedFolder = maxRows; - LLEmojiGridRow* row = nullptr; - if (!mixedFolder && !isupper(category.front())) + // List all categories in the selected group + const auto& group = mFilteredEmojis[mSelectedGroupIndex - 1]; + for (const auto& category : group) { - LLStringUtil::capitalize(category); + // List all emojis in the category + fillEmojisCategory(category.second, category.first, row_panel_params, + row_list_params, icon_params, icon_rect, max_icons, bg_color); } + } - for (const LLEmojiDescriptor* descr : emojis) + if (mEmojiGrid->getPanelList().empty()) + { + showPreview(false); + mFocusedIconRow = -1; + mFocusedIconCol = -1; + if (!mHint.empty()) { - if (sFilterPattern.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)) - { - if (maxRows && ++rowCount > maxRows) - break; - 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, mixedFolder ? LLStringUtil::capitalize(descr->Category) : category); - icon->setMouseEnterCallback([this](LLUICtrl* ctrl, const LLSD&) { onEmojiMouseEnter(ctrl); }); - icon->setMouseLeaveCallback([this](LLUICtrl* ctrl, const LLSD&) { onEmojiMouseLeave(ctrl); }); - icon->setMouseDownCallback([this](LLUICtrl* ctrl, S32, S32, MASK) { onEmojiMouseDown(ctrl); }); - icon->setMouseUpCallback([this](LLUICtrl* ctrl, S32, S32, MASK) { onEmojiMouseUp(ctrl); }); - icon->setBackgroundColor(bgColor); - icon->setBackgroundOpaque(1); - icon->setRect(icon_rect); - row->mList->addPanel(icon, true); - - iconIndex++; - } + hideFloater(); } - }; + } + else + { + showPreview(true); + mFocusedIconRow = 0; + mFocusedIconCol = 0; + moveFocusedIconNext(); + } +} - const std::vector& groups = LLEmojiDictionary::instance().getGroups(); - const LLEmojiDictionary::emoji2descr_map_t& emoji2descr = LLEmojiDictionary::instance().getEmoji2Descr(); - const LLEmojiDictionary::cat2descrs_map_t& category2Descr = LLEmojiDictionary::instance().getCategory2Descrs(); - if (!sSelectedGroupIndex) +void LLFloaterEmojiPicker::fillEmojisCategory(const std::vector& emojis, + const std::string& category, const LLPanel::Params& row_panel_params, const LLUICtrl::Params& row_list_params, + const LLPanel::Params& icon_params, const LLRect& icon_rect, S32 max_icons, const LLColor4& bg) +{ + // Place the category title + std::string title = + category == RECENTLY_USED_CATEGORY ? getString("title_for_recently_used") : + category == FREQUENTLY_USED_CATEGORY ? getString("title_for_frequently_used") : + isupper(category.front()) ? category : LLStringUtil::capitalize(category); + LLEmojiGridDivider* div = new LLEmojiGridDivider(row_panel_params, title); + mEmojiGrid->addPanel(div, true); + + int icon_index = 0; + LLEmojiGridRow* row = nullptr; + + if (mFilterPattern.empty()) { - std::vector recentlyUsed; - for (llwchar emoji : sRecentlyUsed) + const LLEmojiDictionary::emoji2descr_map_t& emoji2descr = LLEmojiDictionary::instance().getEmoji2Descr(); + LLEmojiSearchResult emoji { 0, "", 0, 0 }; + if (category == RECENTLY_USED_CATEGORY) { - auto it = emoji2descr.find(emoji); - if (it != emoji2descr.end()) + for (llwchar code : sRecentlyUsed) { - recentlyUsed.push_back(it->second); + const LLEmojiDictionary::emoji2descr_map_t::const_iterator& e2d = emoji2descr.find(code); + if (e2d != emoji2descr.end() && !e2d->second->ShortCodes.empty()) + { + emoji.Character = code; + emoji.String = e2d->second->ShortCodes.front(); + createEmojiIcon(emoji, category, row_panel_params, row_list_params, icon_params, + icon_rect, max_icons, bg, row, icon_index); + } } } - listCategory(getString("title_for_recently_used"), recentlyUsed, 1); - - std::vector frequentlyUsed; - for (auto& emoji : sFrequentlyUsed) + else if (category == FREQUENTLY_USED_CATEGORY) { - auto it = emoji2descr.find(emoji.first); - if (it != emoji2descr.end()) + for (const auto& code : sFrequentlyUsed) { - frequentlyUsed.push_back(it->second); + const LLEmojiDictionary::emoji2descr_map_t::const_iterator& e2d = emoji2descr.find(code.first); + if (e2d != emoji2descr.end() && !e2d->second->ShortCodes.empty()) + { + emoji.Character = code.first; + emoji.String = e2d->second->ShortCodes.front(); + createEmojiIcon(emoji, category, row_panel_params, row_list_params, icon_params, + icon_rect, max_icons, bg, row, icon_index); + } } } - listCategory(getString("title_for_frequently_used"), frequentlyUsed, 1); - - // List all groups - for (const LLEmojiGroup& group : groups) + else { - // List all categories in group - for (const std::string& category : group.Categories) + const LLEmojiDictionary::cat2descrs_map_t& category2Descr = LLEmojiDictionary::instance().getCategory2Descrs(); + const LLEmojiDictionary::cat2descrs_map_t::const_iterator& c2d = category2Descr.find(category); + if (c2d != category2Descr.end()) { - // List all emojis in category - const LLEmojiDictionary::cat2descrs_map_t::const_iterator& item = category2Descr.find(category); - if (item != category2Descr.end()) + for (const LLEmojiDescriptor* descr : c2d->second) { - listCategory(category, item->second); + emoji.Character = descr->Character; + emoji.String = descr->ShortCodes.front(); + createEmojiIcon(emoji, category, row_panel_params, row_list_params, icon_params, + icon_rect, max_icons, bg, row, icon_index); } } } } else { - // List all categories in the selected group - for (const std::string& category : groups[sSelectedGroupIndex].Categories) + for (const LLEmojiSearchResult& emoji : emojis) { - // List all emojis in category - const LLEmojiDictionary::cat2descrs_map_t::const_iterator& item = category2Descr.find(category); - if (item != category2Descr.end()) - { - listCategory(category, item->second); - } + createEmojiIcon(emoji, category, row_panel_params, row_list_params, icon_params, + icon_rect, max_icons, bg, row, icon_index); } } } -void LLFloaterEmojiPicker::onGroupButtonClick(LLUICtrl* ctrl) +void LLFloaterEmojiPicker::createEmojiIcon(const LLEmojiSearchResult& emoji, + const std::string& category, const LLPanel::Params& row_panel_params, const LLUICtrl::Params& row_list_params, + const LLPanel::Params& icon_params, const LLRect& icon_rect, S32 max_icons, const LLColor4& bg, + LLEmojiGridRow*& row, int& icon_index) { - if (LLButton* button = dynamic_cast(ctrl)) + // Place a new row each (max_icons) icons + if (!(icon_index % max_icons)) { - mFilter->setFocus(TRUE); - - if (button == mGroupButtons[sSelectedGroupIndex] || button->getToggleState()) - return; - - auto it = std::find(mGroupButtons.begin(), mGroupButtons.end(), button); - if (it == mGroupButtons.end()) - return; - - mGroupButtons[sSelectedGroupIndex]->setUseFontColor(FALSE); - mGroupButtons[sSelectedGroupIndex]->setToggleState(FALSE); - sSelectedGroupIndex = it - mGroupButtons.begin(); - mGroupButtons[sSelectedGroupIndex]->setToggleState(TRUE); - mGroupButtons[sSelectedGroupIndex]->setUseFontColor(TRUE); - - LLRect rect = mBadge->getRect(); - rect.mLeft = button->getRect().mLeft; - rect.mRight = button->getRect().mRight; - mBadge->setRect(rect); - - fillEmojis(); + row = new LLEmojiGridRow(row_panel_params, *(const LLScrollingPanelList::Params*)&row_list_params); + mEmojiGrid->addPanel(row, true); } + + // Place a new icon to the current row + LLEmojiGridIcon* icon = new LLEmojiGridIcon(icon_params, emoji); + icon->setMouseEnterCallback([this](LLUICtrl* ctrl, const LLSD&) { onEmojiMouseEnter(ctrl); }); + icon->setMouseLeaveCallback([this](LLUICtrl* ctrl, const LLSD&) { onEmojiMouseLeave(ctrl); }); + icon->setMouseDownCallback([this](LLUICtrl* ctrl, S32, S32, MASK) { onEmojiMouseDown(ctrl); }); + icon->setMouseUpCallback([this](LLUICtrl* ctrl, S32, S32, MASK) { onEmojiMouseUp(ctrl); }); + icon->setBackgroundColor(bg); + icon->setBackgroundOpaque(1); + icon->setRect(icon_rect); + row->mList->addPanel(icon, true); + + icon_index++; } -void LLFloaterEmojiPicker::onFilterChanged() +void LLFloaterEmojiPicker::showPreview(bool show) { - sFilterPattern = mFilter->getText(); - fillEmojis(); + //mPreview->setIcon(nullptr); + mDummy->setVisible(show ? FALSE : TRUE); + mPreview->setVisible(show ? TRUE : FALSE); } -void LLFloaterEmojiPicker::onGridMouseEnter() +void LLFloaterEmojiPicker::onGroupButtonClick(LLUICtrl* ctrl) { - LLFocusableElement* focus = gFocusMgr.getKeyboardFocus(); - if (focus == mEmojiGrid) + if (LLButton* button = dynamic_cast(ctrl)) { - exitArrowMode(); - } - showPreview(true); -} + if (button == mGroupButtons[mSelectedGroupIndex] || button->getToggleState()) + return; -void LLFloaterEmojiPicker::onGridMouseLeave() -{ - showPreview(false); + auto it = std::find(mGroupButtons.begin(), mGroupButtons.end(), button); + if (it == mGroupButtons.end()) + return; + + selectEmojiGroup(it - mGroupButtons.begin()); + } } void LLFloaterEmojiPicker::onGroupButtonMouseEnter(LLUICtrl* ctrl) @@ -626,33 +869,40 @@ void LLFloaterEmojiPicker::onGroupButtonMouseLeave(LLUICtrl* ctrl) void LLFloaterEmojiPicker::onEmojiMouseEnter(LLUICtrl* ctrl) { - if (ctrl) + if (LLEmojiGridIcon* icon = dynamic_cast(ctrl)) { - LLFocusableElement* focus = gFocusMgr.getKeyboardFocus(); - if (focus == mEmojiGrid) + if (mFocusedIcon && mFocusedIcon != icon && mFocusedIcon->isBackgroundVisible()) { - exitArrowMode(); - showPreview(true); + unselectGridIcon(mFocusedIcon); } - if (mHoveredIcon && mHoveredIcon != ctrl) + if (mHoveredIcon && mHoveredIcon != icon) { unselectGridIcon(mHoveredIcon); } - selectGridIcon(ctrl); + selectGridIcon(icon); - mHoveredIcon = ctrl; + mHoveredIcon = icon; } } void LLFloaterEmojiPicker::onEmojiMouseLeave(LLUICtrl* ctrl) { - if (ctrl) + if (LLEmojiGridIcon* icon = dynamic_cast(ctrl)) { - if (ctrl == mHoveredIcon) + if (icon == mHoveredIcon) { - unselectGridIcon(ctrl); + if (icon != mFocusedIcon) + { + unselectGridIcon(icon); + } + mHoveredIcon = nullptr; + } + + if (!mHoveredIcon && mFocusedIcon && !mFocusedIcon->isBackgroundVisible()) + { + selectGridIcon(mFocusedIcon); } } } @@ -672,195 +922,176 @@ void LLFloaterEmojiPicker::onEmojiMouseUp(LLUICtrl* ctrl) make_ui_sound("UISndClickRelease"); } - if (mEmojiPickCallback) + if (LLEmojiGridIcon* icon = dynamic_cast(ctrl)) { - if (LLEmojiGridIcon* icon = dynamic_cast(ctrl)) - { - if (mEmojiPickCallback) - { - mEmojiPickCallback(icon->getEmoji()); - } - } - } -} + LLSD value(wstring_to_utf8str(icon->getText())); + setValue(value); -bool LLFloaterEmojiPicker::enterArrowMode() -{ - S32 rowCount = mEmojiGrid->getPanelList().size(); - if (rowCount) - { - mFocusedIconRow = -1; - mFocusedIconCol = 0; - if (moveFocusedIconDown()) + onCommit(); + + if (!mHint.empty() || !(gKeyboard->currentMask(TRUE) & MASK_SHIFT)) { - showPreview(true); - mEmojiScroll->goToTop(); - mEmojiGrid->setFocus(TRUE); - return true; + hideFloater(); } } - return false; } -void LLFloaterEmojiPicker::exitArrowMode() +void LLFloaterEmojiPicker::selectFocusedIcon() { - if (mFocusedIcon) + if (mFocusedIcon && mFocusedIcon != mHoveredIcon) { unselectGridIcon(mFocusedIcon); - mFocusedIcon = nullptr; } - showPreview(false); - mEmojiScroll->goToTop(); - mFocusedIconRow = mFocusedIconCol = 0; - mFilter->setFocus(TRUE); + // Both mFocusedIconRow and mFocusedIconCol should be already verified + LLEmojiGridRow* row = dynamic_cast(mEmojiGrid->getPanelList()[mFocusedIconRow]); + mFocusedIcon = row ? dynamic_cast(row->mList->getPanelList()[mFocusedIconCol]) : nullptr; + + if (mFocusedIcon && !mHoveredIcon) + { + selectGridIcon(mFocusedIcon); + } } -void LLFloaterEmojiPicker::selectFocusedIcon() +bool LLFloaterEmojiPicker::moveFocusedIconPrev() { if (mHoveredIcon) - { - unselectGridIcon(mHoveredIcon); - } + return false; - if (mFocusedIcon) + if (mFocusedIconCol > 0) { - unselectGridIcon(mFocusedIcon); + mFocusedIconCol--; + selectFocusedIcon(); + return true; } - // Both mFocusedIconRow and mFocusedIconCol should be already verified - LLEmojiGridRow* row = (LLEmojiGridRow*)mEmojiGrid->getPanelList()[mFocusedIconRow]; - mFocusedIcon = row->mList->getPanelList()[mFocusedIconCol]; - selectGridIcon(mFocusedIcon); -} - -bool LLFloaterEmojiPicker::moveFocusedIconUp() -{ for (S32 i = mFocusedIconRow - 1; i >= 0; --i) { LLScrollingPanel* panel = mEmojiGrid->getPanelList()[i]; LLEmojiGridRow* row = dynamic_cast(panel); - if (row && row->mList->getPanelList().size() > mFocusedIconCol) + if (row && row->mList->getPanelList().size()) { mEmojiScroll->scrollToShowRect(row->getBoundingRect()); + mFocusedIconCol = row->mList->getPanelList().size() - 1; mFocusedIconRow = i; selectFocusedIcon(); return true; } } + return false; } -bool LLFloaterEmojiPicker::moveFocusedIconDown() +bool LLFloaterEmojiPicker::moveFocusedIconNext() { + if (mHoveredIcon) + return false; + + LLScrollingPanel* panel = mEmojiGrid->getPanelList()[mFocusedIconRow]; + LLEmojiGridRow* row = dynamic_cast(panel); + S32 colCount = row ? row->mList->getPanelList().size() : 0; + if (mFocusedIconCol < colCount - 1) + { + mFocusedIconCol++; + selectFocusedIcon(); + return true; + } + S32 rowCount = mEmojiGrid->getPanelList().size(); for (S32 i = mFocusedIconRow + 1; i < rowCount; ++i) { LLScrollingPanel* panel = mEmojiGrid->getPanelList()[i]; LLEmojiGridRow* row = dynamic_cast(panel); - if (row && row->mList->getPanelList().size() > mFocusedIconCol) + if (row && row->mList->getPanelList().size()) { mEmojiScroll->scrollToShowRect(row->getBoundingRect()); + mFocusedIconCol = 0; mFocusedIconRow = i; selectFocusedIcon(); return true; } } + return false; } -bool LLFloaterEmojiPicker::moveFocusedIconLeft() +void LLFloaterEmojiPicker::selectGridIcon(LLEmojiGridIcon* icon) { - if (mFocusedIconCol <= 0) - return false; - - mFocusedIconCol--; - selectFocusedIcon(); - return true; + icon->setBackgroundVisible(TRUE); + mPreview->setIcon(icon); } -bool LLFloaterEmojiPicker::moveFocusedIconRight() +void LLFloaterEmojiPicker::unselectGridIcon(LLEmojiGridIcon* icon) { - LLEmojiGridRow* row = (LLEmojiGridRow*)mEmojiGrid->getPanelList()[mFocusedIconRow]; - S32 colCount = row->mList->getPanelList().size(); - if (mFocusedIconCol >= colCount - 1) - return false; - - mFocusedIconCol++; - selectFocusedIcon(); - return true; + icon->setBackgroundVisible(FALSE); + mPreview->setIcon(nullptr); } -void LLFloaterEmojiPicker::selectGridIcon(LLUICtrl* ctrl) +// virtual +BOOL LLFloaterEmojiPicker::handleKey(KEY key, MASK mask, BOOL called_from_parent) { - if (LLEmojiGridIcon* icon = dynamic_cast(ctrl)) + if (mask == MASK_NONE) { - icon->setBackgroundVisible(TRUE); - mPreview->setEmoji(icon->getDescr()); + switch (key) + { + case KEY_LEFT: + selectEmojiGroup((mSelectedGroupIndex + mFilteredEmojis.size()) % mGroupButtons.size()); + return TRUE; + case KEY_RIGHT: + selectEmojiGroup((mSelectedGroupIndex + 1) % mGroupButtons.size()); + return TRUE; + case KEY_UP: + moveFocusedIconPrev(); + return TRUE; + case KEY_DOWN: + moveFocusedIconNext(); + return TRUE; + case KEY_ESCAPE: + hideFloater(); + return TRUE; + } } -} -void LLFloaterEmojiPicker::unselectGridIcon(LLUICtrl* ctrl) -{ - if (LLEmojiGridIcon* icon = dynamic_cast(ctrl)) + if (key == KEY_RETURN) { - icon->setBackgroundVisible(FALSE); - mPreview->setEmoji(nullptr); + U64 time = totalTime(); + // comes twice for unknown reason + if (mFocusedIcon && (time - mRecentReturnPressedMs > 100000)) // Min interval 0.1 sec. + { + onEmojiMouseDown(mFocusedIcon); + onEmojiMouseUp(mFocusedIcon); + } + mRecentReturnPressedMs = time; + return TRUE; } -} -// virtual -BOOL LLFloaterEmojiPicker::handleKey(KEY key, MASK mask, BOOL called_from_parent) -{ - if (mask == MASK_NONE) + if (mHint.empty()) { - LLFocusableElement* focus = gFocusMgr.getKeyboardFocus(); - if (focus == mEmojiGrid) + if (key >= 0x20 && key < 0x80) { - if (key == KEY_RETURN) + if (!mEmojiGrid->getPanelList().empty()) { - if (mFocusedIcon) + if (mFilterPattern.empty()) { - onEmojiMouseDown(mFocusedIcon); - onEmojiMouseUp(mFocusedIcon); - closeFloater(); - return TRUE; + mFilterPattern = ":"; } + mFilterPattern += (char)key; + initialize(); } - else if (key == KEY_TAB) - { - exitArrowMode(); - return TRUE; - } - else if (key == KEY_UP) - { - if (!moveFocusedIconUp()) - exitArrowMode(); - return TRUE; - } - else if (key == KEY_DOWN) - { - if (moveFocusedIconDown()) - return TRUE; - } - else if (key == KEY_LEFT) - { - if (moveFocusedIconLeft()) - return TRUE; - } - else if (key == KEY_RIGHT) - { - if (moveFocusedIconRight()) - return TRUE; - } + return TRUE; } - else // if (focus != mEmojiGrid) + else if (key == KEY_BACKSPACE) { - if (key == KEY_DOWN) + if (!mFilterPattern.empty()) { - if (enterArrowMode()) - return TRUE; + mFilterPattern.pop_back(); + if (mFilterPattern == ":") + { + mFilterPattern.clear(); + } + initialize(); } + return TRUE; } } @@ -868,30 +1099,14 @@ BOOL LLFloaterEmojiPicker::handleKey(KEY key, MASK mask, BOOL called_from_parent } // virtual -BOOL LLFloaterEmojiPicker::handleKeyHere(KEY key, MASK mask) +void LLFloaterEmojiPicker::goneFromFront() { - if (mask == MASK_NONE) - { - switch (key) - { - case KEY_ESCAPE: - closeFloater(); - return TRUE; - } - } - - return super::handleKeyHere(key, mask); + hideFloater(); } -// virtual -void LLFloaterEmojiPicker::closeFloater(bool app_quitting) +void LLFloaterEmojiPicker::hideFloater() const { - saveState(); - super::closeFloater(app_quitting); - if (mFloaterCloseCallback) - { - mFloaterCloseCallback(); - } + LLEmojiHelper::instance().hideHelper(nullptr, true); } // static @@ -938,18 +1153,17 @@ void LLFloaterEmojiPicker::onEmojiUsed(llwchar emoji) } // Append new if not found if (itf == sFrequentlyUsed.end()) - sFrequentlyUsed.push_back(std::make_pair(emoji, 1)); -} - -// static -void LLFloaterEmojiPicker::onRecentlyUsedChanged() -{ - if (sSelectedGroupIndex) - return; - - if (LLFloaterEmojiPicker* picker = getInstance()) { - picker->fillEmojis(); + // Insert before others with count == 1 + while (itf != sFrequentlyUsed.begin()) + { + auto prior = itf; + prior--; + if (prior->second > 1) + break; + itf = prior; + } + sFrequentlyUsed.insert(itf, std::make_pair(emoji, 1)); } } @@ -977,10 +1191,6 @@ void LLFloaterEmojiPicker::loadState() return; } - sSelectedGroupIndex = state[sKeySelectedGroupIndex].asInteger(); - - sFilterPattern = state[sKeyFilterPattern].asString(); - // Load and parse sRecentlyUsed std::string recentlyUsed = state[sKeyRecentlyUsed]; std::vector rtokens = LLStringUtil::getTokens(recentlyUsed, ","); @@ -1028,7 +1238,7 @@ void LLFloaterEmojiPicker::loadState() // Normalize by minimum if (!sFrequentlyUsed.empty()) { - U32 delta = sFrequentlyUsed.back().second; + U32 delta = sFrequentlyUsed.back().second - 1; for (auto& it : sFrequentlyUsed) { it.second = std::max((U32)0, it.second - delta); @@ -1047,16 +1257,6 @@ void LLFloaterEmojiPicker::saveState() LLSD state = LLSD::emptyMap(); - if (sSelectedGroupIndex) - { - state[sKeySelectedGroupIndex] = (int)sSelectedGroupIndex; - } - - if (!sFilterPattern.empty()) - { - state[sKeyFilterPattern] = sFilterPattern; - } - if (!sRecentlyUsed.empty()) { U32 maxCount = 20; -- cgit v1.2.3 From 1820095dd4a64c142e14869c76ae09e1e0ca6a12 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Mon, 12 Feb 2024 19:01:00 +0100 Subject: #779 Emoji picker is an unintuitive UX disaster --- indra/newview/llfloateremojipicker.cpp | 62 ++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 6 deletions(-) (limited to 'indra/newview/llfloateremojipicker.cpp') diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index 2380fd357b..1578caa39c 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -953,6 +953,43 @@ void LLFloaterEmojiPicker::selectFocusedIcon() } } +bool LLFloaterEmojiPicker::moveFocusedIconUp() +{ + for (S32 i = mFocusedIconRow - 1; i >= 0; --i) + { + LLScrollingPanel* panel = mEmojiGrid->getPanelList()[i]; + LLEmojiGridRow* row = dynamic_cast(panel); + if (row && row->mList->getPanelList().size() > mFocusedIconCol) + { + mEmojiScroll->scrollToShowRect(row->getBoundingRect()); + mFocusedIconRow = i; + selectFocusedIcon(); + return true; + } + } + + return false; +} + +bool LLFloaterEmojiPicker::moveFocusedIconDown() +{ + S32 rowCount = mEmojiGrid->getPanelList().size(); + for (S32 i = mFocusedIconRow + 1; i < rowCount; ++i) + { + LLScrollingPanel* panel = mEmojiGrid->getPanelList()[i]; + LLEmojiGridRow* row = dynamic_cast(panel); + if (row && row->mList->getPanelList().size() > mFocusedIconCol) + { + mEmojiScroll->scrollToShowRect(row->getBoundingRect()); + mFocusedIconRow = i; + selectFocusedIcon(); + return true; + } + } + + return false; +} + bool LLFloaterEmojiPicker::moveFocusedIconPrev() { if (mHoveredIcon) @@ -1034,16 +1071,16 @@ BOOL LLFloaterEmojiPicker::handleKey(KEY key, MASK mask, BOOL called_from_parent { switch (key) { - case KEY_LEFT: - selectEmojiGroup((mSelectedGroupIndex + mFilteredEmojis.size()) % mGroupButtons.size()); + case KEY_UP: + moveFocusedIconUp(); return TRUE; - case KEY_RIGHT: - selectEmojiGroup((mSelectedGroupIndex + 1) % mGroupButtons.size()); + case KEY_DOWN: + moveFocusedIconDown(); return TRUE; - case KEY_UP: + case KEY_LEFT: moveFocusedIconPrev(); return TRUE; - case KEY_DOWN: + case KEY_RIGHT: moveFocusedIconNext(); return TRUE; case KEY_ESCAPE: @@ -1052,6 +1089,19 @@ BOOL LLFloaterEmojiPicker::handleKey(KEY key, MASK mask, BOOL called_from_parent } } + if (mask == MASK_ALT) + { + switch (key) + { + case KEY_LEFT: + selectEmojiGroup((mSelectedGroupIndex + mFilteredEmojis.size()) % mGroupButtons.size()); + return TRUE; + case KEY_RIGHT: + selectEmojiGroup((mSelectedGroupIndex + 1) % mGroupButtons.size()); + return TRUE; + } + } + if (key == KEY_RETURN) { U64 time = totalTime(); -- cgit v1.2.3