SL-20356 EmojiPicker - Implement arrow keys navigation

meow-7.2.2
Alexander Gavriliuk 2023-11-07 23:13:00 +01:00 committed by Guru
parent 2e0892e530
commit 716b9af35d
7 changed files with 230 additions and 8 deletions

View File

@ -70,6 +70,7 @@ LLScrollContainer::Params::Params()
bg_color("color"),
border_visible("border_visible"),
hide_scrollbar("hide_scrollbar"),
ignore_arrow_keys("ignore_arrow_keys"),
min_auto_scroll_rate("min_auto_scroll_rate", 100),
max_auto_scroll_rate("max_auto_scroll_rate", 1000),
max_auto_scroll_zone("max_auto_scroll_zone", 16),
@ -86,6 +87,7 @@ LLScrollContainer::LLScrollContainer(const LLScrollContainer::Params& p)
mBackgroundColor(p.bg_color()),
mIsOpaque(p.is_opaque),
mHideScrollbar(p.hide_scrollbar),
mIgnoreArrowKeys(p.ignore_arrow_keys),
mReserveScrollCorner(p.reserve_scroll_corner),
mMinAutoScrollRate(p.min_auto_scroll_rate),
mMaxAutoScrollRate(p.max_auto_scroll_rate),
@ -204,10 +206,29 @@ void LLScrollContainer::reshape(S32 width, S32 height,
}
}
// virtual
BOOL LLScrollContainer::handleKeyHere(KEY key, MASK mask)
{
if (mIgnoreArrowKeys)
{
switch(key)
{
case KEY_LEFT:
case KEY_RIGHT:
case KEY_UP:
case KEY_DOWN:
case KEY_PAGE_UP:
case KEY_PAGE_DOWN:
case KEY_HOME:
case KEY_END:
return FALSE;
default:
break;
}
}
// allow scrolled view to handle keystrokes in case it delegated keyboard focus
// to the scroll container.
// to the scroll container.
// NOTE: this should not recurse indefinitely as handleKeyHere
// should not propagate to parent controls, so mScrolledView should *not*
// call LLScrollContainer::handleKeyHere in turn

View File

@ -63,7 +63,8 @@ public:
Optional<bool> is_opaque,
reserve_scroll_corner,
border_visible,
hide_scrollbar;
hide_scrollbar,
ignore_arrow_keys;
Optional<F32> min_auto_scroll_rate,
max_auto_scroll_rate;
Optional<U32> max_auto_scroll_zone;
@ -149,6 +150,7 @@ private:
F32 mMaxAutoScrollRate;
U32 mMaxAutoScrollZone;
bool mHideScrollbar;
bool mIgnoreArrowKeys;
};

View File

@ -45,7 +45,7 @@ public:
/*
* A set of panels that are displayed in a vertical sequence inside a scroll container.
* A set of panels that are displayed in a sequence inside a scroll container.
*/
class LLScrollingPanelList : public LLUICtrl
{

View File

@ -399,6 +399,13 @@ void LLFloaterEmojiPicker::moveGroups()
mBadge->setRect(rect);
}
void LLFloaterEmojiPicker::showPreview(bool show)
{
mPreview->setEmoji(nullptr);
mDummy->setVisible(show ? FALSE : TRUE);
mPreview->setVisible(show ? TRUE : FALSE);
}
void LLFloaterEmojiPicker::fillEmojis(bool fromResize)
{
mRecentGridWidth = mEmojiScroll->getRect().getWidth();
@ -423,6 +430,9 @@ void LLFloaterEmojiPicker::fillEmojis(bool fromResize)
mRecentMaxIcons = maxIcons;
mFocusedIconRow = 0;
mFocusedIconCol = 0;
mFocusedIcon = nullptr;
mHoveredIcon = nullptr;
mEmojiGrid->clearPanels();
mPreview->setEmoji(nullptr);
@ -599,15 +609,17 @@ void LLFloaterEmojiPicker::onFilterChanged()
void LLFloaterEmojiPicker::onGridMouseEnter()
{
mDummy->setVisible(FALSE);
mPreview->setEmoji(nullptr);
mPreview->setVisible(TRUE);
LLFocusableElement* focus = gFocusMgr.getKeyboardFocus();
if (focus == mEmojiGrid)
{
exitArrowMode();
}
showPreview(true);
}
void LLFloaterEmojiPicker::onGridMouseLeave()
{
mPreview->setVisible(FALSE);
mDummy->setVisible(TRUE);
showPreview(false);
}
void LLFloaterEmojiPicker::onGroupButtonMouseEnter(LLUICtrl* ctrl)
@ -630,6 +642,13 @@ void LLFloaterEmojiPicker::onEmojiMouseEnter(LLUICtrl* ctrl)
{
if (ctrl)
{
LLFocusableElement* focus = gFocusMgr.getKeyboardFocus();
if (focus == mEmojiGrid)
{
exitArrowMode();
showPreview(true);
}
if (mHoveredIcon && mHoveredIcon != ctrl)
{
unselectGridIcon(mHoveredIcon);
@ -679,6 +698,113 @@ void LLFloaterEmojiPicker::onEmojiMouseUp(LLUICtrl* ctrl)
}
}
bool LLFloaterEmojiPicker::enterArrowMode()
{
S32 rowCount = mEmojiGrid->getPanelList().size();
if (rowCount)
{
mFocusedIconRow = -1;
mFocusedIconCol = 0;
if (moveFocusedIconDown())
{
showPreview(true);
mEmojiScroll->goToTop();
mEmojiGrid->setFocus(TRUE);
return true;
}
}
return false;
}
void LLFloaterEmojiPicker::exitArrowMode()
{
if (mFocusedIcon)
{
unselectGridIcon(mFocusedIcon);
mFocusedIcon = nullptr;
}
showPreview(false);
mEmojiScroll->goToTop();
mFocusedIconRow = mFocusedIconCol = 0;
mFilter->setFocus(TRUE);
}
void LLFloaterEmojiPicker::selectFocusedIcon()
{
if (mHoveredIcon)
{
unselectGridIcon(mHoveredIcon);
}
if (mFocusedIcon)
{
unselectGridIcon(mFocusedIcon);
}
// Both mFocusedIconRow and mFocusedIconCol should be already verified
LLEmojiGridRow* row = (LLEmojiGridRow*)mEmojiGrid->getPanelList()[mFocusedIconRow];
mFocusedIcon = row->mList->getPanelList()[mFocusedIconCol];
selectGridIcon(mFocusedIcon);
}
bool LLFloaterEmojiPicker::moveFocusedIconUp()
{
for (S32 i = mFocusedIconRow - 1; i >= 0; --i)
{
LLScrollingPanel* panel = mEmojiGrid->getPanelList()[i];
LLEmojiGridRow* row = dynamic_cast<LLEmojiGridRow*>(panel);
if (row && row->mList->getPanelList().size() > mFocusedIconCol)
{
mEmojiScroll->scrollToShowRect(row->getBoundingRect());
mFocusedIconRow = i;
selectFocusedIcon();
return true;
}
}
return false;
}
bool LLFloaterEmojiPicker::moveFocusedIconDown()
{
S32 rowCount = mEmojiGrid->getPanelList().size();
for (S32 i = mFocusedIconRow + 1; i < rowCount; ++i)
{
LLScrollingPanel* panel = mEmojiGrid->getPanelList()[i];
LLEmojiGridRow* row = dynamic_cast<LLEmojiGridRow*>(panel);
if (row && row->mList->getPanelList().size() > mFocusedIconCol)
{
mEmojiScroll->scrollToShowRect(row->getBoundingRect());
mFocusedIconRow = i;
selectFocusedIcon();
return true;
}
}
return false;
}
bool LLFloaterEmojiPicker::moveFocusedIconLeft()
{
if (mFocusedIconCol <= 0)
return false;
mFocusedIconCol--;
selectFocusedIcon();
return true;
}
bool LLFloaterEmojiPicker::moveFocusedIconRight()
{
LLEmojiGridRow* row = (LLEmojiGridRow*)mEmojiGrid->getPanelList()[mFocusedIconRow];
S32 colCount = row->mList->getPanelList().size();
if (mFocusedIconCol >= colCount - 1)
return false;
mFocusedIconCol++;
selectFocusedIcon();
return true;
}
void LLFloaterEmojiPicker::selectGridIcon(LLUICtrl* ctrl)
{
if (LLEmojiGridIcon* icon = dynamic_cast<LLEmojiGridIcon*>(ctrl))
@ -697,6 +823,64 @@ void LLFloaterEmojiPicker::unselectGridIcon(LLUICtrl* ctrl)
}
}
// virtual
BOOL LLFloaterEmojiPicker::handleKey(KEY key, MASK mask, BOOL called_from_parent)
{
if (mask == MASK_NONE)
{
LLFocusableElement* focus = gFocusMgr.getKeyboardFocus();
if (focus == mEmojiGrid)
{
if (key == KEY_RETURN)
{
if (mFocusedIcon)
{
onEmojiMouseDown(mFocusedIcon);
onEmojiMouseUp(mFocusedIcon);
closeFloater();
return TRUE;
}
}
else if (key == KEY_TAB)
{
exitArrowMode();
return TRUE;
}
else if (key == KEY_UP)
{
if (!moveFocusedIconUp())
exitArrowMode();
return TRUE;
}
else if (key == KEY_DOWN)
{
if (moveFocusedIconDown())
return TRUE;
}
else if (key == KEY_LEFT)
{
if (moveFocusedIconLeft())
return TRUE;
}
else if (key == KEY_RIGHT)
{
if (moveFocusedIconRight())
return TRUE;
}
}
else // if (focus != mEmojiGrid)
{
if (key == KEY_DOWN)
{
if (enterArrowMode())
return TRUE;
}
}
}
return super::handleKey(key, mask, called_from_parent);
}
// virtual
BOOL LLFloaterEmojiPicker::handleKeyHere(KEY key, MASK mask)
{

View File

@ -63,6 +63,7 @@ public:
private:
void fillGroups();
void moveGroups();
void showPreview(bool show);
void fillEmojis(bool fromResize = false);
void onGroupButtonClick(LLUICtrl* ctrl);
@ -76,9 +77,18 @@ private:
void onEmojiMouseDown(LLUICtrl* ctrl);
void onEmojiMouseUp(LLUICtrl* ctrl);
bool enterArrowMode();
void exitArrowMode();
void selectFocusedIcon();
bool moveFocusedIconUp();
bool moveFocusedIconDown();
bool moveFocusedIconLeft();
bool moveFocusedIconRight();
void selectGridIcon(LLUICtrl* ctrl);
void unselectGridIcon(LLUICtrl* ctrl);
virtual BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent) override;
virtual BOOL handleKeyHere(KEY key, MASK mask) override;
class LLPanel* mGroups { nullptr };
@ -97,6 +107,9 @@ private:
S32 mRecentBadgeWidth { 0 };
S32 mRecentGridWidth { 0 };
S32 mRecentMaxIcons { 0 };
S32 mFocusedIconRow { 0 };
S32 mFocusedIconCol { 0 };
LLUICtrl* mFocusedIcon { nullptr };
LLUICtrl* mHoveredIcon { nullptr };
};

View File

@ -3054,6 +3054,7 @@ BOOL LLViewerWindow::handleKey(KEY key, MASK mask)
case KEY_PAGE_UP:
case KEY_PAGE_DOWN:
case KEY_HOME:
case KEY_END:
// when chatbar is empty or ArrowKeysAlwaysMove set,
// pass arrow keys on to avatar...
return FALSE;

View File

@ -25,6 +25,7 @@
name="EmojiGridContainer"
layout="topleft"
follows="all"
ignore_arrow_keys="true"
top="50"
left="0"
height="300"