diff options
-rw-r--r-- | indra/llui/llemojidictionary.cpp | 132 | ||||
-rw-r--r-- | indra/llui/llemojidictionary.h | 1 | ||||
-rw-r--r-- | indra/llui/llemojihelper.cpp | 7 | ||||
-rw-r--r-- | indra/llui/llemojihelper.h | 4 | ||||
-rw-r--r-- | indra/llui/llfloater.cpp | 28 | ||||
-rw-r--r-- | indra/llui/llfloater.h | 5 | ||||
-rw-r--r-- | indra/llui/lltexteditor.cpp | 24 | ||||
-rw-r--r-- | indra/llui/lltexteditor.h | 1 | ||||
-rw-r--r-- | indra/llui/lluictrl.cpp | 25 | ||||
-rw-r--r-- | indra/newview/llfloateremojipicker.cpp | 1066 | ||||
-rw-r--r-- | indra/newview/llfloateremojipicker.h | 66 | ||||
-rw-r--r-- | indra/newview/llfloaterimsessiontab.cpp | 81 | ||||
-rw-r--r-- | indra/newview/llfloaterimsessiontab.h | 10 | ||||
-rw-r--r-- | indra/newview/skins/default/xui/en/floater_emoji_picker.xml | 34 | ||||
-rw-r--r-- | indra/newview/skins/default/xui/en/floater_im_session.xml | 5 |
15 files changed, 833 insertions, 656 deletions
diff --git a/indra/llui/llemojidictionary.cpp b/indra/llui/llemojidictionary.cpp index e29f3436cf..f16c38a11a 100644 --- a/indra/llui/llemojidictionary.cpp +++ b/indra/llui/llemojidictionary.cpp @@ -41,9 +41,9 @@ static const std::string SKINNED_EMOJI_FILENAME("emoji_characters.xml"); static const std::string SKINNED_CATEGORY_FILENAME("emoji_categories.xml"); static const std::string COMMON_GROUP_FILENAME("emoji_groups.xml"); -static const std::string GROUP_NAME_ALL("all"); -static const std::string GROUP_NAME_OTHERS("others"); static const std::string GROUP_NAME_SKIP("skip"); +// https://www.compart.com/en/unicode/U+1F302 +static const S32 GROUP_OTHERS_IMAGE_INDEX = 0x1F302; // ============================================================================ // Helper functions @@ -143,68 +143,76 @@ LLWString LLEmojiDictionary::findMatchingEmojis(const std::string& needle) const return result; } -void LLEmojiDictionary::findByShortCode(std::vector<LLEmojiSearchResult>& result, const std::string& needle) const +// static +bool LLEmojiDictionary::searchInShortCode(std::size_t& begin, std::size_t& end, const std::string& shortCode, const std::string& needle) +{ + begin = 0; + end = 1; + std::size_t index = 1; + // Search for begin + char d = tolower(needle[index++]); + while (end < shortCode.size()) + { + char s = tolower(shortCode[end++]); + if (s == d) + { + begin = end - 1; + break; + } + } + if (!begin) + return false; + // Search for end + d = tolower(needle[index++]); + if (!d) + return true; + while (end < shortCode.size() && index <= needle.size()) + { + char s = tolower(shortCode[end++]); + if (s == d) + { + if (index == needle.size()) + return true; + d = tolower(needle[index++]); + continue; + } + switch (s) + { + case L'-': + case L'_': + case L'+': + continue; + } + break; + } + return false; +} + +void LLEmojiDictionary::findByShortCode( + std::vector<LLEmojiSearchResult>& result, + const std::string& needle +) const { result.clear(); if (needle.empty() || needle.front() != ':') return; - auto search = [needle](std::size_t& begin, std::size_t& end, const std::string& shortCode) -> bool + std::map<llwchar, std::vector<LLEmojiSearchResult>> results; + + for (const LLEmojiDescriptor& d : mEmojis) + { + if (!d.ShortCodes.empty()) { - begin = 0; - end = 1; - std::size_t index = 1; - // Search for begin - char d = tolower(needle[index++]); - while (end < shortCode.size()) + const std::string& shortCode = d.ShortCodes.front(); + if (shortCode.size() >= needle.size() && shortCode.front() == needle.front()) { - char s = tolower(shortCode[end++]); - if (s == d) + std::size_t begin, end; + if (searchInShortCode(begin, end, shortCode, needle)) { - begin = end - 1; - break; + results[begin].emplace_back(d.Character, shortCode, begin, end); } } - if (!begin) - return false; - // Search for end - d = tolower(needle[index++]); - while (end < shortCode.size() && index <= needle.size()) - { - char s = tolower(shortCode[end++]); - if (s == d) - { - if (index == needle.size()) - return true; - d = tolower(needle[index++]); - continue; - } - switch (s) - { - case L'-': - case L'_': - case L'+': - continue; - } - break; - } - return false; - }; - - std::map<std::size_t, std::vector<LLEmojiSearchResult>> results; - - for (const LLEmojiDescriptor& d : mEmojis) - { - if (d.ShortCodes.empty()) - continue; - const std::string& shortCode = d.ShortCodes.front(); - if (shortCode.size() < needle.size() || shortCode.front() != needle.front()) - continue; - std::size_t begin, end; - if (search(begin, end, shortCode)) - { - results[begin].emplace_back(d.Character, shortCode, begin, end); } } @@ -312,27 +320,13 @@ void LLEmojiDictionary::loadGroups() } mGroups.clear(); - // Add group "all" - mGroups.emplace_back(); - // https://www.compart.com/en/unicode/U+1F50D - mGroups.front().Character = 0x1F50D; - // https://www.compart.com/en/unicode/U+1F302 - llwchar iconOthers = 0x1F302; // Register all groups for (LLSD::array_const_iterator it = data.beginArray(), end = data.endArray(); it != end; ++it) { const LLSD& sd = *it; const std::string& name = sd["Name"].asStringRef(); - if (name == GROUP_NAME_ALL) - { - mGroups.front().Character = loadIcon(sd); - } - else if (name == GROUP_NAME_OTHERS) - { - iconOthers = loadIcon(sd); - } - else if (name == GROUP_NAME_SKIP) + if (name == GROUP_NAME_SKIP) { mSkipCategories = loadCategories(sd); translateCategories(mSkipCategories); @@ -355,7 +349,7 @@ void LLEmojiDictionary::loadGroups() // Add group "others" mGroups.emplace_back(); - mGroups.back().Character = iconOthers; + mGroups.back().Character = GROUP_OTHERS_IMAGE_INDEX; } void LLEmojiDictionary::loadEmojis() diff --git a/indra/llui/llemojidictionary.h b/indra/llui/llemojidictionary.h index 66b564b70a..4af376df64 100644 --- a/indra/llui/llemojidictionary.h +++ b/indra/llui/llemojidictionary.h @@ -89,6 +89,7 @@ public: static void initClass(); LLWString findMatchingEmojis(const std::string& needle) const; + static bool searchInShortCode(std::size_t& begin, std::size_t& end, const std::string& shortCode, const std::string& needle); void findByShortCode(std::vector<LLEmojiSearchResult>& result, const std::string& needle) const; const LLEmojiDescriptor* getDescriptorFromEmoji(llwchar emoji) const; const LLEmojiDescriptor* getDescriptorFromShortCode(const std::string& short_code) const; diff --git a/indra/llui/llemojihelper.cpp b/indra/llui/llemojihelper.cpp index 9411f7cac5..89e6ddf987 100644 --- a/indra/llui/llemojihelper.cpp +++ b/indra/llui/llemojihelper.cpp @@ -36,7 +36,7 @@ // Constants // -constexpr char DEFAULT_EMOJI_HELPER_FLOATER[] = "emoji_complete"; +constexpr char DEFAULT_EMOJI_HELPER_FLOATER[] = "emoji_picker"; constexpr S32 HELPER_FLOATER_OFFSET_X = 0; constexpr S32 HELPER_FLOATER_OFFSET_Y = 0; @@ -117,9 +117,10 @@ void LLEmojiHelper::showHelper(LLUICtrl* hostctrl_p, S32 local_x, S32 local_y, c pHelperFloater->openFloater(LLSD().with("hint", short_code)); } -void LLEmojiHelper::hideHelper(const LLUICtrl* ctrl_p) +void LLEmojiHelper::hideHelper(const LLUICtrl* ctrl_p, bool strict) { - if (ctrl_p && !isActive(ctrl_p)) + mIsHideDisabled &= !strict; + if (mIsHideDisabled || (ctrl_p && !isActive(ctrl_p))) { return; } diff --git a/indra/llui/llemojihelper.h b/indra/llui/llemojihelper.h index 58f68d12a4..e826ff93e6 100644 --- a/indra/llui/llemojihelper.h +++ b/indra/llui/llemojihelper.h @@ -45,7 +45,8 @@ public: bool isActive(const LLUICtrl* ctrl_p) const; static bool isCursorInEmojiCode(const LLWString& wtext, S32 cursor_pos, S32* short_code_pos_p = nullptr); void showHelper(LLUICtrl* hostctrl_p, S32 local_x, S32 local_y, const std::string& short_code, std::function<void(llwchar)> commit_cb); - void hideHelper(const LLUICtrl* ctrl_p = nullptr); + void hideHelper(const LLUICtrl* ctrl_p = nullptr, bool strict = false); + void setIsHideDisabled(bool disabled) { mIsHideDisabled = disabled; }; // Eventing bool handleKey(const LLUICtrl* ctrl_p, KEY key, MASK mask); @@ -61,4 +62,5 @@ private: boost::signals2::connection mHostCtrlFocusLostConn; boost::signals2::connection mHelperCommitConn; std::function<void(llwchar)> mEmojiCommitCb; + bool mIsHideDisabled; }; diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index 23d3a66631..31019f6c33 100644 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -182,6 +182,7 @@ LLFloater::Params::Params() save_visibility("save_visibility", false), can_dock("can_dock", false), show_title("show_title", true), + auto_close("auto_close", false), positioning("positioning", LLFloaterEnums::POSITIONING_RELATIVE), header_height("header_height", 0), legacy_header_height("legacy_header_height", 0), @@ -254,6 +255,7 @@ LLFloater::LLFloater(const LLSD& key, const LLFloater::Params& p) mCanClose(p.can_close), mDragOnLeft(p.can_drag_on_left), mResizable(p.can_resize), + mAutoClose(p.auto_close), mPositioning(p.positioning), mMinWidth(p.min_width), mMinHeight(p.min_height), @@ -681,7 +683,7 @@ void LLFloater::openFloater(const LLSD& key) if (getHost() != NULL) { getHost()->setMinimized(FALSE); - getHost()->setVisibleAndFrontmost(mAutoFocus); + getHost()->setVisibleAndFrontmost(mAutoFocus && !getIsChrome()); getHost()->showFloater(this); } else @@ -693,7 +695,7 @@ void LLFloater::openFloater(const LLSD& key) } applyControlsAndPosition(floater_to_stack); setMinimized(FALSE); - setVisibleAndFrontmost(mAutoFocus); + setVisibleAndFrontmost(mAutoFocus && !getIsChrome()); } mOpenSignal(this, key); @@ -1551,7 +1553,7 @@ void LLFloater::addDependentFloater(LLFloater* floaterp, BOOL reposition, BOOL r if (floaterp->isFrontmost()) { // make sure to bring self and sibling floaters to front - gFloaterView->bringToFront(floaterp); + gFloaterView->bringToFront(floaterp, floaterp->getAutoFocus() && !getIsChrome()); } } @@ -1696,6 +1698,7 @@ BOOL LLFloater::handleDoubleClick(S32 x, S32 y, MASK mask) return was_minimized || LLPanel::handleDoubleClick(x, y, mask); } +// virtual void LLFloater::bringToFront( S32 x, S32 y ) { if (getVisible() && pointInView(x, y)) @@ -1710,12 +1713,20 @@ void LLFloater::bringToFront( S32 x, S32 y ) LLFloaterView* parent = dynamic_cast<LLFloaterView*>( getParent() ); if (parent) { - parent->bringToFront( this ); + parent->bringToFront(this, !getIsChrome()); } } } } +// virtual +void LLFloater::goneFromFront() +{ + if (mAutoClose) + { + closeFloater(); + } +} // virtual void LLFloater::setVisibleAndFrontmost(BOOL take_focus,const LLSD& key) @@ -2561,6 +2572,11 @@ void LLFloaterView::bringToFront(LLFloater* child, BOOL give_focus, BOOL restore return; } + if (mFrontChild && !mFrontChild->isDead()) + { + mFrontChild->goneFromFront(); + } + mFrontChild = child; // *TODO: make this respect floater's mAutoFocus value, instead of @@ -3060,6 +3076,9 @@ LLFloater *LLFloaterView::getBackmost() const void LLFloaterView::syncFloaterTabOrder() { + if (mFrontChild && !mFrontChild->isDead() && mFrontChild->getIsChrome()) + return; + // look for a visible modal dialog, starting from first LLModalDialog* modal_dialog = NULL; for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) @@ -3307,6 +3326,7 @@ void LLFloater::initFromParams(const LLFloater::Params& p) mDefaultRelativeY = p.rel_y; mPositioning = p.positioning; + mAutoClose = p.auto_close; mSaveRect = p.save_rect; if (p.save_visibility) diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h index 860a6f9e2a..5f4e1a2cad 100644 --- a/indra/llui/llfloater.h +++ b/indra/llui/llfloater.h @@ -163,7 +163,8 @@ public: save_visibility, save_dock_state, can_dock, - show_title; + show_title, + auto_close; Optional<LLFloaterEnums::EOpenPositioning> positioning; @@ -390,6 +391,7 @@ protected: void setInstanceName(const std::string& name); virtual void bringToFront(S32 x, S32 y); + virtual void goneFromFront(); void setExpandedRect(const LLRect& rect) { mExpandedRect = rect; } // size when not minimized const LLRect& getExpandedRect() const { return mExpandedRect; } @@ -488,6 +490,7 @@ private: bool mFocusStealsFrontmost = true; // FALSE if we don't want the currently focused floater to cover this floater without user interaction BOOL mDragOnLeft; BOOL mResizable; + BOOL mAutoClose; LLFloaterEnums::EOpenPositioning mPositioning; LLCoordFloater mPosition; diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index 3910be1443..092739a538 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -508,7 +508,8 @@ void LLTextEditor::getSegmentsInRange(LLTextEditor::segment_vec_t& segments_out, } } -void LLTextEditor::setShowEmojiHelper(bool show) { +void LLTextEditor::setShowEmojiHelper(bool show) +{ if (!mShowEmojiHelper) { LLEmojiHelper::instance().hideHelper(this); @@ -1192,6 +1193,16 @@ void LLTextEditor::addChar(llwchar wc) } } +void LLTextEditor::showEmojiHelper() +{ + if (mReadOnly || !mShowEmojiHelper) + return; + + const LLRect cursorRect(getLocalRectFromDocIndex(mCursorPos)); + auto cb = [this](llwchar emoji) { insertEmoji(emoji); }; + LLEmojiHelper::instance().showHelper(this, cursorRect.mLeft, cursorRect.mTop, LLStringUtil::null, cb); +} + void LLTextEditor::tryToShowEmojiHelper() { if (mReadOnly || !mShowEmojiHelper) @@ -1207,6 +1218,10 @@ void LLTextEditor::tryToShowEmojiHelper() auto cb = [this](llwchar emoji) { handleEmojiCommit(emoji); }; LLEmojiHelper::instance().showHelper(this, cursorRect.mLeft, cursorRect.mTop, part, cb); } + else + { + LLEmojiHelper::instance().hideHelper(); + } } void LLTextEditor::addLineBreakChar(BOOL group_together) @@ -1911,7 +1926,12 @@ BOOL LLTextEditor::handleUnicodeCharHere(llwchar uni_char) // Handle most keys only if the text editor is writeable. if( !mReadOnly ) { - if( mAutoIndent && '}' == uni_char ) + if (mShowEmojiHelper && uni_char < 0x80 && LLEmojiHelper::instance().handleKey(this, (KEY)uni_char, MASK_NONE)) + { + return TRUE; + } + + if( mAutoIndent && '}' == uni_char ) { unindentLineBeforeCloseBrace(); } diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h index 7a96e17899..521405ec25 100644 --- a/indra/llui/lltexteditor.h +++ b/indra/llui/lltexteditor.h @@ -206,6 +206,7 @@ public: void setShowContextMenu(bool show) { mShowContextMenu = show; } bool getShowContextMenu() const { return mShowContextMenu; } + void showEmojiHelper(); void setShowEmojiHelper(bool show); bool getShowEmojiHelper() const { return mShowEmojiHelper; } diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp index 21afcae7c3..7eb9ae69fb 100644 --- a/indra/llui/lluictrl.cpp +++ b/indra/llui/lluictrl.cpp @@ -768,25 +768,20 @@ void LLUICtrl::setIsChrome(BOOL is_chrome) // virtual BOOL LLUICtrl::getIsChrome() const -{ +{ + if (mIsChrome) + return TRUE; + LLView* parent_ctrl = getParent(); - while(parent_ctrl) + while (parent_ctrl) { - if(parent_ctrl->isCtrl()) - { - break; - } + if (parent_ctrl->isCtrl()) + return ((LLUICtrl*)parent_ctrl)->getIsChrome(); + parent_ctrl = parent_ctrl->getParent(); } - - if(parent_ctrl) - { - return mIsChrome || ((LLUICtrl*)parent_ctrl)->getIsChrome(); - } - else - { - return mIsChrome ; - } + + return FALSE; } 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<llwchar> sRecentlyUsed; static std::list<std::pair<llwchar, U32>> 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<LLFloaterEmojiPicker>("emoji_picker"); - if (!floater) - LL_ERRS() << "Cannot instantiate emoji picker" << LL_ENDL; - return floater; -} - -LLFloaterEmojiPicker* LLFloaterEmojiPicker::showInstance(pick_callback_t pick_callback, close_callback_t close_callback) -{ - LLFloaterEmojiPicker* floater = getInstance(); - floater->show(pick_callback, close_callback); - return floater; -} - -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<LLPanel>("Groups"); + mBadge = getChild<LLPanel>("Badge"); + mEmojiScroll = getChild<LLScrollContainer>("EmojiGridContainer"); + mEmojiGrid = getChild<LLScrollingPanelList>("EmojiGrid"); + mDummy = getChild<LLTextBox>("Dummy"); + mPreview = new LLEmojiPreviewPanel(); mPreview->setVisible(FALSE); addChild(mPreview); - mDummy = getChild<LLTextBox>("Dummy"); - - mGroups = getChild<LLPanel>("Groups"); - mBadge = getChild<LLPanel>("Badge"); - - mFilter = getChild<LLFilterEditor>("Filter"); - mFilter->setKeystrokeCallback([this](LLUICtrl*, const LLSD&) { onFilterChanged(); }); - mFilter->setTextChangedCallback([this](LLUICtrl*, const LLSD&) { onFilterChanged(); }); - mFilter->setText(sFilterPattern); + return LLFloater::postBuild(); +} - mEmojiScroll = getChild<LLScrollContainer>("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<LLScrollingPanelList>("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<std::string, std::vector<LLEmojiSearchResult>> 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<LLEmojiGroup>& 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<LLButton>(params); - button->setClickedCallback([this](LLUICtrl* ctrl, const LLSD&) { onGroupButtonClick(ctrl); }); - button->setMouseEnterCallback([this](LLUICtrl* ctrl, const LLSD&) { onGroupButtonMouseEnter(ctrl); }); - button->setMouseLeaveCallback([this](LLUICtrl* ctrl, const LLSD&) { onGroupButtonMouseLeave(ctrl); }); + std::map<std::string, std::vector<LLEmojiSearchResult>> 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<std::string, std::vector<LLEmojiSearchResult>>& cats) +{ + if (sRecentlyUsed.empty()) + return; - mGroupButtons.push_back(button); - mGroups->addChild(button); + std::vector<LLEmojiSearchResult> 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<std::string, std::vector<LLEmojiSearchResult>>& cats) { - const std::vector<LLEmojiGroup>& groups = LLEmojiDictionary::instance().getGroups(); - if (groups.empty()) + if (sFrequentlyUsed.empty()) return; - int badgeWidth = mGroups->getRect().getWidth() / groups.size(); - if (badgeWidth == mRecentBadgeWidth) + std::vector<LLEmojiSearchResult> 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<std::string, std::vector<LLEmojiSearchResult>>& cats, U32 index) +{ + const std::vector<LLEmojiGroup>& 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<LLEmojiSearchResult> 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<LLButton>(params); + button->setClickedCallback([this](LLUICtrl* ctrl, const LLSD&) { onGroupButtonClick(ctrl); }); + button->setMouseEnterCallback([this](LLUICtrl* ctrl, const LLSD&) { onGroupButtonMouseEnter(ctrl); }); + button->setMouseLeaveCallback([this](LLUICtrl* ctrl, const LLSD&) { onGroupButtonMouseLeave(ctrl); }); + + button->setRect(rect); + 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<S32> 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<const LLEmojiDescriptor*>& 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<LLEmojiGroup>& 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<LLEmojiSearchResult>& 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<const LLEmojiDescriptor*> 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<const LLEmojiDescriptor*> 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<LLButton*>(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<LLButton*>(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<LLEmojiGridIcon*>(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<LLEmojiGridIcon*>(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<LLEmojiGridIcon*>(ctrl)) { - if (LLEmojiGridIcon* icon = dynamic_cast<LLEmojiGridIcon*>(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<LLEmojiGridRow*>(mEmojiGrid->getPanelList()[mFocusedIconRow]); + mFocusedIcon = row ? dynamic_cast<LLEmojiGridIcon*>(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<LLEmojiGridRow*>(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<LLEmojiGridRow*>(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<LLEmojiGridRow*>(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<LLEmojiGridIcon*>(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<LLEmojiGridIcon*>(ctrl)) + if (key == KEY_RETURN) { - icon->setBackgroundVisible(FALSE); - mPreview->setEmoji(nullptr); + U64 time = totalTime(); + // <Shift+Return> 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<std::string> 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; diff --git a/indra/newview/llfloateremojipicker.h b/indra/newview/llfloateremojipicker.h index 48f66d7950..f169d67a4e 100644 --- a/indra/newview/llfloateremojipicker.h +++ b/indra/newview/llfloateremojipicker.h @@ -29,7 +29,10 @@ #include "llfloater.h" +class LLEmojiGridRow; +class LLEmojiGridIcon; struct LLEmojiDescriptor; +struct LLEmojiSearchResult; class LLFloaterEmojiPicker : public LLFloater { @@ -40,36 +43,40 @@ public: typedef boost::function<void (llwchar)> pick_callback_t; typedef boost::function<void ()> close_callback_t; - // Call this to select an emoji. - static LLFloaterEmojiPicker* getInstance(); - static LLFloaterEmojiPicker* showInstance(pick_callback_t pick_callback = nullptr, close_callback_t close_callback = nullptr); - LLFloaterEmojiPicker(const LLSD& key); - virtual ~LLFloaterEmojiPicker(); virtual BOOL postBuild() override; virtual void dirtyRect() override; + virtual void goneFromFront() override; - void show(pick_callback_t pick_callback = nullptr, close_callback_t close_callback = nullptr); - - virtual void closeFloater(bool app_quitting = false) override; + void hideFloater() const; static std::list<llwchar>& getRecentlyUsed(); static void onEmojiUsed(llwchar emoji); - static void onRecentlyUsedChanged(); + static void loadState(); static void saveState(); private: + void initialize(); void fillGroups(); - void moveGroups(); - void showPreview(bool show); + void fillCategoryRecentlyUsed(std::map<std::string, std::vector<LLEmojiSearchResult>>& cats); + void fillCategoryFrequentlyUsed(std::map<std::string, std::vector<LLEmojiSearchResult>>& cats); + void fillGroupEmojis(std::map<std::string, std::vector<LLEmojiSearchResult>>& cats, U32 index); + void createGroupButton(LLButton::Params& params, const LLRect& rect, llwchar emoji); + void resizeGroupButtons(); + void selectEmojiGroup(U32 index); void fillEmojis(bool fromResize = false); + void fillEmojisCategory(const std::vector<LLEmojiSearchResult>& 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); + void 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); + void showPreview(bool show); void onGroupButtonClick(LLUICtrl* ctrl); - void onFilterChanged(); - void onGridMouseEnter(); - void onGridMouseLeave(); void onGroupButtonMouseEnter(LLUICtrl* ctrl); void onGroupButtonMouseLeave(LLUICtrl* ctrl); void onEmojiMouseEnter(LLUICtrl* ctrl); @@ -77,40 +84,37 @@ private: void onEmojiMouseDown(LLUICtrl* ctrl); void onEmojiMouseUp(LLUICtrl* ctrl); - bool enterArrowMode(); - void exitArrowMode(); void selectFocusedIcon(); - bool moveFocusedIconUp(); - bool moveFocusedIconDown(); - bool moveFocusedIconLeft(); - bool moveFocusedIconRight(); + bool moveFocusedIconPrev(); + bool moveFocusedIconNext(); - void selectGridIcon(LLUICtrl* ctrl); - void unselectGridIcon(LLUICtrl* ctrl); + void selectGridIcon(LLEmojiGridIcon* icon); + void unselectGridIcon(LLEmojiGridIcon* icon); + void onOpen(const LLSD& key) override; virtual BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent) override; - virtual BOOL handleKeyHere(KEY key, MASK mask) override; class LLPanel* mGroups { nullptr }; class LLPanel* mBadge { nullptr }; - class LLFilterEditor* mFilter { nullptr }; class LLScrollContainer* mEmojiScroll { nullptr }; class LLScrollingPanelList* mEmojiGrid { nullptr }; class LLEmojiPreviewPanel* mPreview { nullptr }; class LLTextBox* mDummy { nullptr }; - pick_callback_t mEmojiPickCallback; - close_callback_t mFloaterCloseCallback; - + std::vector<S32> mFilteredEmojiGroups; + std::vector<std::map<std::string, std::vector<LLEmojiSearchResult>>> mFilteredEmojis; std::vector<class LLButton*> mGroupButtons; - S32 mRecentBadgeWidth { 0 }; - S32 mRecentGridWidth { 0 }; + std::string mHint; + std::string mFilterPattern; + U32 mSelectedGroupIndex { 0 }; S32 mRecentMaxIcons { 0 }; S32 mFocusedIconRow { 0 }; S32 mFocusedIconCol { 0 }; - LLUICtrl* mFocusedIcon { nullptr }; - LLUICtrl* mHoveredIcon { nullptr }; + LLEmojiGridIcon* mFocusedIcon { nullptr }; + LLEmojiGridIcon* mHoveredIcon { nullptr }; + + U64 mRecentReturnPressedMs { 0 }; }; #endif diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp index 35b2af7b84..1e0540c88a 100644 --- a/indra/newview/llfloaterimsessiontab.cpp +++ b/indra/newview/llfloaterimsessiontab.cpp @@ -259,7 +259,7 @@ BOOL LLFloaterIMSessionTab::postBuild() mEmojiRecentPanelToggleBtn = getChild<LLButton>("emoji_recent_panel_toggle_btn"); mEmojiRecentPanelToggleBtn->setLabel(LLUIString(LLWString(1, 128512))); - mEmojiRecentPanelToggleBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiRecentPanelToggleBtnClicked(this); }); + mEmojiRecentPanelToggleBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiRecentPanelToggleBtnClicked(); }); mEmojiRecentPanel = getChild<LLLayoutPanel>("emoji_recent_layout_panel"); mEmojiRecentPanel->setVisible(false); @@ -272,8 +272,8 @@ BOOL LLFloaterIMSessionTab::postBuild() mEmojiRecentIconsCtrl->setCommitCallback([this](LLUICtrl*, const LLSD& value) { onRecentEmojiPicked(value); }); mEmojiRecentIconsCtrl->setVisible(false); - mEmojiPickerToggleBtn = getChild<LLButton>("emoji_picker_toggle_btn"); - mEmojiPickerToggleBtn->setClickedCallback([](LLUICtrl*, const LLSD&) { onEmojiPickerToggleBtnClicked(); }); + mEmojiPickerShowBtn = getChild<LLButton>("emoji_picker_show_btn"); + mEmojiPickerShowBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiPickerShowBtnClicked(); }); mGearBtn = getChild<LLButton>("gear_btn"); mAddBtn = getChild<LLButton>("add_btn"); @@ -452,56 +452,30 @@ void LLFloaterIMSessionTab::onInputEditorClicked() gToolBarView->flashCommand(LLCommandId("chat"), false); } -void LLFloaterIMSessionTab::onEmojiRecentPanelToggleBtnClicked(LLFloaterIMSessionTab* self) +void LLFloaterIMSessionTab::onEmojiRecentPanelToggleBtnClicked() { - bool show = !self->mEmojiRecentPanel->getVisible(); - bool restore_focus = !show || (gFocusMgr.getLastKeyboardFocus() == self->mInputEditor); - + BOOL show = mEmojiRecentPanel->getVisible() ? FALSE : TRUE; if (show) { - self->initEmojiRecentPanel(!restore_focus); + initEmojiRecentPanel(); } - self->mEmojiRecentPanel->setVisible(show ? TRUE : FALSE); - - if (restore_focus) - { - self->mInputEditor->setFocus(true); - } + mEmojiRecentPanel->setVisible(show); + mInputEditor->setFocus(TRUE); } -void LLFloaterIMSessionTab::onEmojiPickerToggleBtnClicked() +void LLFloaterIMSessionTab::onEmojiPickerShowBtnClicked() { - if (LLFloaterEmojiPicker* picker = LLFloaterEmojiPicker::getInstance()) - { - if (!picker->isShown()) - { - picker->show( - [](llwchar emoji) { onEmojiPicked(emoji); }, - []() { onEmojiPickerClosed(); }); - if (LLFloaterIMContainer* im_box = LLFloaterIMContainer::findInstance()) - { - im_box->addDependentFloater(picker, TRUE, TRUE); - } - } - else - { - picker->closeFloater(); - } - } + mInputEditor->showEmojiHelper(); } -void LLFloaterIMSessionTab::initEmojiRecentPanel(bool moveFocus) +void LLFloaterIMSessionTab::initEmojiRecentPanel() { std::list<llwchar>& recentlyUsed = LLFloaterEmojiPicker::getRecentlyUsed(); if (recentlyUsed.empty()) { mEmojiRecentEmptyText->setVisible(TRUE); mEmojiRecentIconsCtrl->setVisible(FALSE); - if (moveFocus) - { - mEmojiPickerToggleBtn->setFocus(TRUE); - } } else { @@ -513,10 +487,6 @@ void LLFloaterIMSessionTab::initEmojiRecentPanel(bool moveFocus) mEmojiRecentIconsCtrl->setEmojis(emojis); mEmojiRecentEmptyText->setVisible(FALSE); mEmojiRecentIconsCtrl->setVisible(TRUE); - if (moveFocus) - { - mEmojiRecentIconsCtrl->setFocus(TRUE); - } } } @@ -534,31 +504,6 @@ void LLFloaterIMSessionTab::onRecentEmojiPicked(const LLSD& value) } } -// static -void LLFloaterIMSessionTab::onEmojiPicked(llwchar emoji) -{ - if (LLFloaterIMContainer* im_box = LLFloaterIMContainer::findInstance()) - { - if (LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(im_box->getSelectedSession())) - { - session_floater->mInputEditor->insertEmoji(emoji); - session_floater->mInputEditor->setFocus(TRUE); - } - } -} - -// static -void LLFloaterIMSessionTab::onEmojiPickerClosed() -{ - if (LLFloaterIMContainer* im_box = LLFloaterIMContainer::findInstance()) - { - if (LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(im_box->getSelectedSession())) - { - session_floater->mInputEditor->setFocus(TRUE); - } - } -} - void LLFloaterIMSessionTab::closeFloater(bool app_quitting) { LLFloaterEmojiPicker::saveState(); @@ -624,11 +569,11 @@ void LLFloaterIMSessionTab::updateUsedEmojis(LLWString text) if (!emojiSent) return; - LLFloaterEmojiPicker::onRecentlyUsedChanged(); + LLFloaterEmojiPicker::saveState(); if (mEmojiRecentPanel->getVisible()) { - initEmojiRecentPanel(false); + initEmojiRecentPanel(); } } diff --git a/indra/newview/llfloaterimsessiontab.h b/indra/newview/llfloaterimsessiontab.h index ddffed57a3..cc985b2753 100644 --- a/indra/newview/llfloaterimsessiontab.h +++ b/indra/newview/llfloaterimsessiontab.h @@ -189,7 +189,7 @@ protected: LLButton* mExpandCollapseBtn; LLButton* mTearOffBtn; LLButton* mEmojiRecentPanelToggleBtn; - LLButton* mEmojiPickerToggleBtn; + LLButton* mEmojiPickerShowBtn; LLButton* mCloseBtn; LLButton* mGearBtn; LLButton* mAddBtn; @@ -215,12 +215,10 @@ private: void onInputEditorClicked(); - static void onEmojiRecentPanelToggleBtnClicked(LLFloaterIMSessionTab* self); - static void onEmojiPickerToggleBtnClicked(); - void initEmojiRecentPanel(bool moveFocus); + void onEmojiRecentPanelToggleBtnClicked(); + void onEmojiPickerShowBtnClicked(); + void initEmojiRecentPanel(); void onRecentEmojiPicked(const LLSD& value); - static void onEmojiPicked(llwchar emoji); - static void onEmojiPickerClosed(); bool checkIfTornOff(); bool mIsHostAttached; diff --git a/indra/newview/skins/default/xui/en/floater_emoji_picker.xml b/indra/newview/skins/default/xui/en/floater_emoji_picker.xml index a89d97d8e9..d21f8c82bc 100644 --- a/indra/newview/skins/default/xui/en/floater_emoji_picker.xml +++ b/indra/newview/skins/default/xui/en/floater_emoji_picker.xml @@ -3,33 +3,26 @@ name="emojipicker" title="CHOOSE EMOJI" help_topic="emojipicker" - positioning="cascading" - legacy_header_height="0" + single_instance="true" + can_minimize="false" + can_tear_off="false" can_resize="true" + auto_close="true" layout="topleft" min_width="250" - height="400" - width="250"> + chrome="true" + height="350" + width="304"> <floater.string name="title_for_recently_used" value="Recently used"/> <floater.string name="title_for_frequently_used" value="Frequently used"/> - <filter_editor - name="Filter" - label="Start typing to filter" - layout="topleft" - follows="top|left|right" - top="27" - left="10" - height="23" - width="230" /> <scroll_container name="EmojiGridContainer" layout="topleft" follows="all" ignore_arrow_keys="true" - top="50" + top="25" left="0" - height="300" - width="250"> + height="275"> <scrolling_panel_list name="EmojiGrid" layout="topleft" @@ -37,8 +30,7 @@ padding="4" spacing="0" top="0" - left="0" - width="250"/> + left="0"/> </scroll_container> <panel name="Groups" @@ -46,8 +38,7 @@ follows="top|left|right" top="0" left="0" - height="25" - width="250"> + height="25"> <panel name="Badge" layout="bottomleft" @@ -70,6 +61,5 @@ valign="center" bottom="14" left="10" - height="23" - width="230">No emoji selected</text> + height="25">No emoji selected</text> </floater> diff --git a/indra/newview/skins/default/xui/en/floater_im_session.xml b/indra/newview/skins/default/xui/en/floater_im_session.xml index eb5d88bba6..94400f6501 100644 --- a/indra/newview/skins/default/xui/en/floater_im_session.xml +++ b/indra/newview/skins/default/xui/en/floater_im_session.xml @@ -306,6 +306,7 @@ follows="right|bottom" use_font_color="true" font="EmojiLarge" + tab_stop="false" image_hover_unselected="Toolbar_Middle_Over" image_selected="Toolbar_Middle_Selected" image_unselected="Toolbar_Middle_Off" @@ -353,17 +354,19 @@ name="emoji_recent_icons_ctrl" follows="top|left|right" layout="topleft" + tab_stop="false" max_visible="20" top="0" left="1" right="-65" height="30"/> <button - name="emoji_picker_toggle_btn" + name="emoji_picker_show_btn" label="More" tool_tip="Shows/hides emoji picker" follows="right|bottom" layout="topleft" + tab_stop="false" bottom="-5" right="-3" height="20" |