diff options
Diffstat (limited to 'indra/newview')
| -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 | 
6 files changed, 701 insertions, 561 deletions
| 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" | 
