diff options
author | Alexander Gavriliuk <alexandrgproductengine@lindenlab.com> | 2023-07-04 07:38:05 +0200 |
---|---|---|
committer | Guru <alexandrgproductengine@lindenlab.com> | 2023-07-05 10:07:19 +0200 |
commit | 8bbbce015b6dae1bdafe0bba329463322642ca85 (patch) | |
tree | 82b01abfaad33f9bdd3586d50bf045c91b84953b /indra/newview/llfloateremojipicker.cpp | |
parent | 886c6089672cc24a4ce5d2f84f388087e7d6281b (diff) |
SL-19575 Rework emoji picker layout similar to Slack
Diffstat (limited to 'indra/newview/llfloateremojipicker.cpp')
-rw-r--r-- | indra/newview/llfloateremojipicker.cpp | 375 |
1 files changed, 355 insertions, 20 deletions
diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index 7fbaaaaa89..8c7f38d324 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -30,7 +30,10 @@ #include "llcombobox.h" #include "llemojidictionary.h" #include "llfloaterreg.h" +#include "llkeyboard.h" #include "lllineeditor.h" +#include "llscrollcontainer.h" +#include "llscrollingpanellist.h" #include "llscrolllistctrl.h" #include "llscrolllistitem.h" #include "lltextbox.h" @@ -39,6 +42,7 @@ std::string LLFloaterEmojiPicker::mSelectedCategory; std::string LLFloaterEmojiPicker::mSearchPattern; int LLFloaterEmojiPicker::mSelectedEmojiIndex; +bool LLFloaterEmojiPicker::mUseGrid = true; class LLEmojiScrollListItem : public LLScrollListItem { @@ -85,6 +89,114 @@ private: llwchar mEmoji; }; +class LLEmojiGridRow : public LLScrollingPanel +{ +public: + LLEmojiGridRow(const LLPanel::Params& panel_params, + const LLScrollingPanelList::Params& list_params) + : LLScrollingPanel(panel_params) + , mList(new LLScrollingPanelList(list_params)) + { + addChild(mList); + } + + virtual void updatePanel(BOOL allow_modify) + { + } + +public: + LLScrollingPanelList* mList; +}; + +class LLEmojiGridDivider : public LLScrollingPanel +{ +public: + LLEmojiGridDivider(const LLPanel::Params& panel_params, std::string text) + : LLScrollingPanel(panel_params) + , mText(utf8string_to_wstring(text)) + { + } + + virtual void draw() override + { + LLScrollingPanel::draw(); + + F32 x = 4; // padding-left + F32 y = getRect().getHeight() / 2; + LLFontGL::getFontSansSerifBold()->render( + mText, // wstr + 0, // begin_offset + x, // x + y, // y + LLColor4::white, // color + LLFontGL::LEFT, // halign + LLFontGL::VCENTER, // valign + LLFontGL::NORMAL, // style + LLFontGL::DROP_SHADOW_SOFT, // shadow + mText.size(), // max_chars + S32_MAX, // max_pixels + nullptr, // right_x + false, // use_ellipses + true); // use_color + } + + virtual void updatePanel(BOOL allow_modify) + { + } + +private: + const LLWString mText; +}; + +class LLEmojiGridIcon : public LLScrollingPanel +{ +public: + LLEmojiGridIcon(const LLPanel::Params& panel_params, const LLEmojiDescriptor* descr, std::string category) + : LLScrollingPanel(panel_params) + , mEmoji(descr->Character) + , mText(LLWString(1, mEmoji)) + , mDescr(descr->Name) + , mCategory(category) + { + } + + virtual void draw() override + { + LLScrollingPanel::draw(); + + F32 x = getRect().getWidth() / 2; + F32 y = getRect().getHeight() / 2; + LLFontGL::getFontEmoji()->render( + mText, // wstr + 0, // begin_offset + x, // x + y, // y + LLColor4::white, // color + LLFontGL::HCENTER, // halign + LLFontGL::VCENTER, // valign + LLFontGL::NORMAL, // style + LLFontGL::DROP_SHADOW_SOFT, // shadow + 1, // max_chars + S32_MAX, // max_pixels + nullptr, // right_x + false, // use_ellipses + true); // use_color + } + + virtual void updatePanel(BOOL allow_modify) {} + + llwchar getEmoji() const { return mEmoji; } + LLWString getText() const { return mText; } + std::string getDescr() const { return mDescr; } + std::string getCategory() const { return mCategory; } + +private: + const llwchar mEmoji; + const LLWString mText; + const std::string mDescr; + const std::string mCategory; +}; + LLFloaterEmojiPicker* LLFloaterEmojiPicker::getInstance() { LLFloaterEmojiPicker* floater = LLFloaterReg::getTypedInstance<LLFloaterEmojiPicker>("emoji_picker"); @@ -102,6 +214,9 @@ LLFloaterEmojiPicker* LLFloaterEmojiPicker::showInstance(pick_callback_t pick_ca void LLFloaterEmojiPicker::show(pick_callback_t pick_callback, close_callback_t close_callback) { + // Temporary solution to support both layouts + mUseGrid = !gKeyboard->getKeyDown(KEY_SHIFT); + mEmojiPickCallback = pick_callback; mFloaterCloseCallback = close_callback; openFloater(mKey); @@ -119,6 +234,9 @@ BOOL LLFloaterEmojiPicker::postBuild() mPreviewEmoji = getChild<LLButton>("PreviewEmoji"); mPreviewEmoji->setClickedCallback([this](LLUICtrl*, const LLSD&) { onPreviewEmojiClick(); }); + mDescription = getChild<LLTextBox>("Description"); + mDescription->setVisible(FALSE); + mCategory = getChild<LLComboBox>("Category"); mCategory->setCommitCallback([this](LLUICtrl*, const LLSD&) { onCategoryCommit(); }); const LLEmojiDictionary::cat2descrs_map_t& cat2Descrs = LLEmojiDictionary::instance().getCategory2Descrs(); @@ -137,14 +255,33 @@ BOOL LLFloaterEmojiPicker::postBuild() mSearch->setFont(LLViewerChat::getChatFont()); mSearch->setText(mSearchPattern); - mEmojis = getChild<LLScrollListCtrl>("Emojis"); - mEmojis->setCommitCallback([this](LLUICtrl*, const LLSD&) { onEmojiSelect(); }); - mEmojis->setDoubleClickCallback([this]() { onEmojiPick(); }); + mEmojiList = getChild<LLScrollListCtrl>("EmojiList"); + mEmojiList->setCommitCallback([this](LLUICtrl*, const LLSD&) { onEmojiSelect(); }); + mEmojiList->setDoubleClickCallback([this]() { onEmojiPick(); }); + mEmojiList->setVisible(!mUseGrid); + + mEmojiScroll = getChild<LLScrollContainer>("EmojiGridContainer"); + mEmojiScroll->setVisible(mUseGrid); + mEmojiScroll->setMouseEnterCallback([this](LLUICtrl*, const LLSD&) { onGridMouseEnter(); }); + mEmojiScroll->setMouseLeaveCallback([this](LLUICtrl*, const LLSD&) { onGridMouseLeave(); }); + + mEmojiGrid = getChild<LLScrollingPanelList>("EmojiGrid"); + fillEmojis(); return TRUE; } +void LLFloaterEmojiPicker::dirtyRect() +{ + super::dirtyRect(); + + if (mUseGrid && mEmojiScroll && mEmojiScroll->getRect().getWidth() != mRecentGridWidth) + { + fillEmojiGrid(); + } +} + LLFloaterEmojiPicker::~LLFloaterEmojiPicker() { gFocusMgr.releaseFocusIfNeeded( this ); @@ -152,7 +289,15 @@ LLFloaterEmojiPicker::~LLFloaterEmojiPicker() void LLFloaterEmojiPicker::fillEmojis() { - mEmojis->clearRows(); + if (mUseGrid) + fillEmojiGrid(); + else + fillEmojiList(); +} + +void LLFloaterEmojiPicker::fillEmojiList() +{ + mEmojiList->clearRows(); const LLEmojiDictionary::emoji2descr_map_t& emoji2Descr = LLEmojiDictionary::instance().getEmoji2Descr(); for (const LLEmojiDictionary::emoji2descr_item_t& item : emoji2Descr) @@ -169,17 +314,17 @@ void LLFloaterEmojiPicker::fillEmojis() // The following line adds default monochrome view of the emoji (is shown as an example) //params.columns.add().column("look").value(wstring_to_utf8str(LLWString(1, it.first))); params.columns.add().column("name").value(descr->Name); - mEmojis->addRow(new LLEmojiScrollListItem(item.first, params), params); + mEmojiList->addRow(new LLEmojiScrollListItem(item.first, params), params); } - if (mEmojis->getItemCount()) + if (mEmojiList->getItemCount()) { - if (mSelectedEmojiIndex > 0 && mSelectedEmojiIndex < mEmojis->getItemCount()) - mEmojis->selectNthItem(mSelectedEmojiIndex); + if (mSelectedEmojiIndex > 0 && mSelectedEmojiIndex < mEmojiList->getItemCount()) + mEmojiList->selectNthItem(mSelectedEmojiIndex); else - mEmojis->selectFirstItem(); + mEmojiList->selectFirstItem(); - mEmojis->scrollToShowSelected(); + mEmojiList->scrollToShowSelected(); } else { @@ -187,6 +332,103 @@ void LLFloaterEmojiPicker::fillEmojis() } } +void LLFloaterEmojiPicker::fillEmojiGrid() +{ + mEmojiGrid->clearPanels(); + + S32 scrollbarSize = mEmojiScroll->getSize(); + if (scrollbarSize < 0) + { + static LLUICachedControl<S32> scrollbar_size_control("UIScrollbarSize", 0); + scrollbarSize = scrollbar_size_control; + } + + mRecentGridWidth = mEmojiScroll->getRect().getWidth(); + const S32 clientWidth = mRecentGridWidth - scrollbarSize - mEmojiScroll->getBorderWidth() * 2; + if (mEmojiGrid->getRect().getWidth() != clientWidth) + { + LLRect rect = mEmojiGrid->getRect(); + rect.mRight = rect.mLeft + clientWidth; + mEmojiGrid->setRect(rect); + } + + const S32 gridPadding = mEmojiGrid->getPadding(); + const S32 iconSpacing = mEmojiGrid->getSpacing(); + const S32 rowWidth = clientWidth - gridPadding * 2; + const S32 iconSize = 28; // icon width and height + const S32 maxIcons = llmax(1, (rowWidth + iconSpacing) / (iconSize + iconSpacing)); + + LLPanel::Params row_panel_params; + row_panel_params.rect = LLRect(0, iconSize, rowWidth, 0); + + LLScrollingPanelList::Params row_list_params; + row_list_params.rect = row_panel_params.rect; + row_list_params.is_horizontal = TRUE; + row_list_params.padding = 0; + row_list_params.spacing = iconSpacing; + + LLPanel::Params icon_params; + LLRect icon_rect(0, iconSize, iconSize, 0); + + auto listCategory = [&](std::string category, const std::vector<const LLEmojiDescriptor*>& emojis, bool showDivider) + { + int iconIndex = 0; + LLEmojiGridRow* row = nullptr; + LLStringUtil::capitalize(category); + for (const LLEmojiDescriptor* descr : emojis) + { + if (mSearchPattern.empty() || matchesPattern(descr)) + { + // Place a category title if needed + if (showDivider) + { + LLEmojiGridDivider* div = new LLEmojiGridDivider(row_panel_params, category); + mEmojiGrid->addPanel(div, true); + showDivider = false; + } + + // Place a new row each (maxIcons) icons + if (!(iconIndex % maxIcons)) + { + row = new LLEmojiGridRow(row_panel_params, row_list_params); + mEmojiGrid->addPanel(row, true); + } + + // Place a new icon to the current row + LLEmojiGridIcon* icon = new LLEmojiGridIcon(icon_params, descr, category); + icon->setMouseEnterCallback([this](LLUICtrl* ctrl, const LLSD&) { onEmojiMouseEnter(ctrl); }); + icon->setMouseLeaveCallback([this](LLUICtrl* ctrl, const LLSD&) { onEmojiMouseLeave(ctrl); }); + icon->setMouseUpCallback([this](LLUICtrl* ctrl, S32, S32, MASK mask) { onEmojiMouseClick(ctrl, mask); }); + icon->setRect(icon_rect); + row->mList->addPanel(icon, true); + + iconIndex++; + } + } + }; + + const LLEmojiDictionary::cat2descrs_map_t& category2Descr = LLEmojiDictionary::instance().getCategory2Descrs(); + if (mSelectedCategory.empty()) + { + // List all categories with titles + for (const LLEmojiDictionary::cat2descrs_item_t& item : category2Descr) + { + listCategory(item.first, item.second, TRUE); + } + } + else + { + // List one category without title + const LLEmojiDictionary::cat2descrs_map_t::const_iterator& item = category2Descr.find(mSelectedCategory); + if (item != category2Descr.end()) + { + listCategory(mSelectedCategory, item->second, FALSE); + } + } + + onEmojiEmpty(); +} + bool LLFloaterEmojiPicker::matchesCategory(const LLEmojiDescriptor* descr) { return std::find(descr->Categories.begin(), descr->Categories.end(), mSelectedCategory) != descr->Categories.end(); @@ -223,19 +465,85 @@ void LLFloaterEmojiPicker::onPreviewEmojiClick() { if (mEmojiPickCallback) { - if (LLEmojiScrollListItem* item = dynamic_cast<LLEmojiScrollListItem*>(mEmojis->getFirstSelected())) + if (mUseGrid) { - mEmojiPickCallback(item->getEmoji()); + if (LLEmojiGridIcon* icon = dynamic_cast<LLEmojiGridIcon*>(mHoveredIcon)) + { + mEmojiPickCallback(icon->getEmoji()); + } + } + else + { + if (LLEmojiScrollListItem* item = dynamic_cast<LLEmojiScrollListItem*>(mEmojiList->getFirstSelected())) + { + mEmojiPickCallback(item->getEmoji()); + } + } + } +} + +void LLFloaterEmojiPicker::onGridMouseEnter() +{ + mSearch->setVisible(FALSE); + mDescription->setText(LLStringExplicit(""), LLStyle::Params()); + mDescription->setVisible(TRUE); +} + +void LLFloaterEmojiPicker::onGridMouseLeave() +{ + mDescription->setVisible(FALSE); + mDescription->setText(LLStringExplicit(""), LLStyle::Params()); + mSearch->setVisible(TRUE); + mSearch->setFocus(TRUE); +} + +void LLFloaterEmojiPicker::onEmojiMouseEnter(LLUICtrl* ctrl) +{ + if (ctrl) + { + if (mHoveredIcon && mHoveredIcon != ctrl) + { + unselectGridIcon(mHoveredIcon); + } + + selectGridIcon(ctrl); + + mHoveredIcon = ctrl; + } +} + +void LLFloaterEmojiPicker::onEmojiMouseLeave(LLUICtrl* ctrl) +{ + if (ctrl) + { + if (ctrl == mHoveredIcon) + { + unselectGridIcon(ctrl); + } + } +} + +void LLFloaterEmojiPicker::onEmojiMouseClick(LLUICtrl* ctrl, MASK mask) +{ + if (mEmojiPickCallback) + { + if (LLEmojiGridIcon* icon = dynamic_cast<LLEmojiGridIcon*>(ctrl)) + { + mPreviewEmoji->handleAnyMouseClick(0, 0, 0, EMouseClickType::CLICK_LEFT, TRUE); + mPreviewEmoji->handleAnyMouseClick(0, 0, 0, EMouseClickType::CLICK_LEFT, FALSE); + if (!(mask & 4)) + { + closeFloater(); + } } } } void LLFloaterEmojiPicker::onEmojiSelect() { - const LLEmojiScrollListItem* item = dynamic_cast<LLEmojiScrollListItem*>(mEmojis->getFirstSelected()); - if (item) + if (LLEmojiScrollListItem* item = dynamic_cast<LLEmojiScrollListItem*>(mEmojiList->getFirstSelected())) { - mSelectedEmojiIndex = mEmojis->getFirstSelectedIndex(); + mSelectedEmojiIndex = mEmojiList->getFirstSelectedIndex(); LLUIString text; text.insert(0, LLWString(1, item->getEmoji())); mPreviewEmoji->setLabel(text); @@ -255,7 +563,7 @@ void LLFloaterEmojiPicker::onEmojiPick() { if (mEmojiPickCallback) { - if (LLEmojiScrollListItem* item = dynamic_cast<LLEmojiScrollListItem*>(mEmojis->getFirstSelected())) + if (LLEmojiScrollListItem* item = dynamic_cast<LLEmojiScrollListItem*>(mEmojiList->getFirstSelected())) { mEmojiPickCallback(item->getEmoji()); closeFloater(); @@ -263,6 +571,31 @@ void LLFloaterEmojiPicker::onEmojiPick() } } +void LLFloaterEmojiPicker::selectGridIcon(LLUICtrl* ctrl) +{ + if (LLEmojiGridIcon* icon = dynamic_cast<LLEmojiGridIcon*>(ctrl)) + { + icon->setBackgroundVisible(TRUE); + + LLUIString text; + text.insert(0, icon->getText()); + mPreviewEmoji->setLabel(text); + + std::string descr = icon->getDescr() + "\n" + icon->getCategory(); + mDescription->setText(LLStringExplicit(descr), LLStyle::Params()); + } +} + +void LLFloaterEmojiPicker::unselectGridIcon(LLUICtrl* ctrl) +{ + if (LLEmojiGridIcon* icon = dynamic_cast<LLEmojiGridIcon*>(ctrl)) + { + icon->setBackgroundVisible(FALSE); + mPreviewEmoji->setLabel(LLUIString()); + mDescription->setText(LLStringExplicit(""), LLStyle::Params()); + } +} + // virtual BOOL LLFloaterEmojiPicker::handleKeyHere(KEY key, MASK mask) { @@ -279,12 +612,12 @@ BOOL LLFloaterEmojiPicker::handleKeyHere(KEY key, MASK mask) closeFloater(); return TRUE; case KEY_UP: - mEmojis->selectPrevItem(); - mEmojis->scrollToShowSelected(); + mEmojiList->selectPrevItem(); + mEmojiList->scrollToShowSelected(); return TRUE; case KEY_DOWN: - mEmojis->selectNextItem(); - mEmojis->scrollToShowSelected(); + mEmojiList->selectNextItem(); + mEmojiList->scrollToShowSelected(); return TRUE; } } @@ -297,5 +630,7 @@ void LLFloaterEmojiPicker::closeFloater(bool app_quitting) { LLFloater::closeFloater(app_quitting); if (mFloaterCloseCallback) + { mFloaterCloseCallback(); + } } |