summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrey Kleshchev <andreykproductengine@lindenlab.com>2023-06-27 00:24:13 +0300
committerAndrey Kleshchev <andreykproductengine@lindenlab.com>2023-06-27 00:29:34 +0300
commitcd0c40c71af767dd66694dc3329e08823aa6b6e7 (patch)
tree2cd1b5d91968fb20581d3ecc5124a45a249482a5
parent434f2ee512dc6edf4a6dfbafd7265d121038bf0b (diff)
SL-19904 Outfit gallery keyboard support
-rw-r--r--indra/newview/lloutfitgallery.cpp353
-rw-r--r--indra/newview/lloutfitgallery.h31
-rw-r--r--indra/newview/skins/default/xui/en/panel_outfit_gallery.xml42
3 files changed, 356 insertions, 70 deletions
diff --git a/indra/newview/lloutfitgallery.cpp b/indra/newview/lloutfitgallery.cpp
index 415d0a96d7..de988555c5 100644
--- a/indra/newview/lloutfitgallery.cpp
+++ b/indra/newview/lloutfitgallery.cpp
@@ -146,6 +146,264 @@ void LLOutfitGallery::draw()
}
}
+BOOL LLOutfitGallery::handleKeyHere(KEY key, MASK mask)
+{
+ BOOL handled = FALSE;
+ switch (key)
+ {
+ case KEY_RETURN:
+ // Open selected items if enter key hit on the inventory panel
+ if (mask == MASK_NONE && mSelectedOutfitUUID.notNull())
+ {
+ // Or should it wearSelectedOutfit?
+ getSelectedItem()->openOutfitsContent();
+ }
+ handled = TRUE;
+ break;
+ case KEY_DELETE:
+#if LL_DARWIN
+ case KEY_BACKSPACE:
+#endif
+ // Delete selected items if delete or backspace key hit on the inventory panel
+ // Note: on Mac laptop keyboards, backspace and delete are one and the same
+ if (mSelectedOutfitUUID.notNull())
+ {
+ onRemoveOutfit(mSelectedOutfitUUID);
+ }
+ handled = TRUE;
+ break;
+
+ case KEY_F2:
+ LLAppearanceMgr::instance().renameOutfit(mSelectedOutfitUUID);
+ handled = TRUE;
+ break;
+
+ case KEY_PAGE_UP:
+ if (mScrollPanel)
+ {
+ mScrollPanel->pageUp(30);
+ }
+ handled = TRUE;
+ break;
+
+ case KEY_PAGE_DOWN:
+ if (mScrollPanel)
+ {
+ mScrollPanel->pageDown(30);
+ }
+ handled = TRUE;
+ break;
+
+ case KEY_HOME:
+ if (mScrollPanel)
+ {
+ mScrollPanel->goToTop();
+ }
+ handled = TRUE;
+ break;
+
+ case KEY_END:
+ if (mScrollPanel)
+ {
+ mScrollPanel->goToBottom();
+ }
+ handled = TRUE;
+ break;
+
+ case KEY_LEFT:
+ moveLeft();
+ handled = TRUE;
+ break;
+
+ case KEY_RIGHT:
+ moveRight();
+ handled = TRUE;
+ break;
+
+ case KEY_UP:
+ moveUp();
+ handled = TRUE;
+ break;
+
+ case KEY_DOWN:
+ moveDown();
+ handled = TRUE;
+ break;
+
+ default:
+ break;
+ }
+
+ if (handled)
+ {
+ mOutfitGalleryMenu->hide();
+ }
+
+ return handled;
+}
+
+void LLOutfitGallery::moveUp()
+{
+ if (mSelectedOutfitUUID.notNull() && mItemsAddedCount > 1)
+ {
+ LLOutfitGalleryItem* item = getSelectedItem();
+ if (item)
+ {
+ S32 n = mItemIndexMap[item];
+ n -= mItemsInRow;
+ if (n >= 0)
+ {
+ item = mIndexToItemMap[n];
+ LLUUID item_id = item->getUUID();
+ ChangeOutfitSelection(nullptr, item_id);
+ item->setFocus(TRUE);
+
+ scrollToShowItem(mSelectedOutfitUUID);
+ }
+ }
+ }
+}
+
+void LLOutfitGallery::moveDown()
+{
+ if (mSelectedOutfitUUID.notNull() && mItemsAddedCount > 1)
+ {
+ LLOutfitGalleryItem* item = getSelectedItem();
+ if (item)
+ {
+ S32 n = mItemIndexMap[item];
+ n += mItemsInRow;
+ if (n < mItemsAddedCount)
+ {
+ item = mIndexToItemMap[n];
+ LLUUID item_id = item->getUUID();
+ ChangeOutfitSelection(nullptr, item_id);
+ item->setFocus(TRUE);
+
+ scrollToShowItem(mSelectedOutfitUUID);
+ }
+ }
+ }
+}
+
+void LLOutfitGallery::moveLeft()
+{
+ if (mSelectedOutfitUUID.notNull() && mItemsAddedCount > 1)
+ {
+ LLOutfitGalleryItem* item = getSelectedItem();
+ if (item)
+ {
+ // Might be better to get item from panel
+ S32 n = mItemIndexMap[item];
+ n--;
+ if (n < 0)
+ {
+ n = mItemsAddedCount - 1;
+ }
+ item = mIndexToItemMap[n];
+ LLUUID item_id = item->getUUID();
+ ChangeOutfitSelection(nullptr, item_id);
+ item->setFocus(TRUE);
+
+ scrollToShowItem(mSelectedOutfitUUID);
+ }
+ }
+}
+
+void LLOutfitGallery::moveRight()
+{
+ if (mSelectedOutfitUUID.notNull() && mItemsAddedCount > 1)
+ {
+ LLOutfitGalleryItem* item = getSelectedItem();
+ if (item)
+ {
+ S32 n = mItemIndexMap[item];
+ n++;
+ if (n == mItemsAddedCount)
+ {
+ n = 0;
+ }
+ item = mIndexToItemMap[n];
+ LLUUID item_id = item->getUUID();
+ ChangeOutfitSelection(nullptr, item_id);
+ item->setFocus(TRUE);
+
+ scrollToShowItem(mSelectedOutfitUUID);
+ }
+ }
+}
+
+void LLOutfitGallery::onFocusLost()
+{
+ LLOutfitListBase::onFocusLost();
+
+ if (mSelectedOutfitUUID.notNull())
+ {
+ LLOutfitGalleryItem* item = getSelectedItem();
+ if (item)
+ {
+ item->setSelected(false);
+ }
+ }
+}
+
+void LLOutfitGallery::onFocusReceived()
+{
+ LLOutfitListBase::onFocusReceived();
+
+ if (mSelectedOutfitUUID.notNull())
+ {
+ LLOutfitGalleryItem* item = getSelectedItem();
+ if (item)
+ {
+ item->setSelected(true);
+ }
+ }
+}
+
+void LLOutfitGallery::onRemoveOutfit(const LLUUID& outfit_cat_id)
+{
+ LLNotificationsUtil::add("DeleteOutfits", LLSD(), LLSD(), boost::bind(onOutfitsRemovalConfirmation, _1, _2, outfit_cat_id));
+}
+
+void LLOutfitGallery::onOutfitsRemovalConfirmation(const LLSD& notification, const LLSD& response, const LLUUID& outfit_cat_id)
+{
+ S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
+ if (option != 0) return; // canceled
+
+ if (outfit_cat_id.notNull())
+ {
+ gInventory.removeCategory(outfit_cat_id);
+ }
+}
+
+void LLOutfitGallery::scrollToShowItem(const LLUUID& item_id)
+{
+ LLOutfitGalleryItem* item = mOutfitMap[item_id];
+ if (item)
+ {
+ const LLRect visible_content_rect = mScrollPanel->getVisibleContentRect();
+
+ LLRect item_rect;
+ item->localRectToOtherView(item->getLocalRect(), &item_rect, mScrollPanel);
+ LLRect overlap_rect(item_rect);
+ overlap_rect.intersectWith(visible_content_rect);
+
+ //Scroll when the selected item is outside the visible area
+ if (overlap_rect.getHeight() + 5 < item->getRect().getHeight())
+ {
+ LLRect content_rect = mScrollPanel->getContentWindowRect();
+ LLRect constraint_rect;
+ constraint_rect.setOriginAndSize(0, 0, content_rect.getWidth(), content_rect.getHeight());
+
+ LLRect item_doc_rect;
+ item->localRectToOtherView(item->getLocalRect(), &item_doc_rect, mGalleryPanel);
+
+ mScrollPanel->scrollToShowRect(item_doc_rect, constraint_rect);
+ }
+ }
+}
+
void LLOutfitGallery::updateRowsIfNeeded()
{
if(((getRect().getWidth() - mRowPanelWidth) > mItemWidth) && mRowCount > 1)
@@ -266,8 +524,9 @@ void LLOutfitGallery::addToGallery(LLOutfitGalleryItem* item)
mHiddenItems.push_back(item);
return;
}
+ mItemIndexMap[item] = mItemsAddedCount;
+ mIndexToItemMap[mItemsAddedCount] = item;
mItemsAddedCount++;
- mItemIndexMap[item] = mItemsAddedCount - 1;
int n = mItemsAddedCount;
int row_count = (n % mItemsInRow) == 0 ? n / mItemsInRow : n / mItemsInRow + 1;
int n_prev = n - 1;
@@ -303,6 +562,7 @@ void LLOutfitGallery::removeFromGalleryLast(LLOutfitGalleryItem* item)
int row_count = (n % mItemsInRow) == 0 ? n / mItemsInRow : n / mItemsInRow + 1;
int row_count_prev = (n_prev % mItemsInRow) == 0 ? n_prev / mItemsInRow : n_prev / mItemsInRow + 1;
mItemsAddedCount--;
+ mIndexToItemMap.erase(mItemsAddedCount);
bool remove_row = row_count != row_count_prev;
removeFromLastRow(mItems[mItemsAddedCount]);
@@ -328,6 +588,7 @@ void LLOutfitGallery::removeFromGalleryMiddle(LLOutfitGalleryItem* item)
}
int n = mItemIndexMap[item];
mItemIndexMap.erase(item);
+ mIndexToItemMap.erase(n);
std::vector<LLOutfitGalleryItem*> saved;
for (int i = mItemsAddedCount - 1; i > n; i--)
{
@@ -361,9 +622,15 @@ LLOutfitGalleryItem* LLOutfitGallery::buildGalleryItem(std::string name, LLUUID
gitem->setFollowsTop();
gitem->setOutfitName(name);
gitem->setUUID(outfit_id);
+ gitem->setGallery(this);
return gitem;
}
+LLOutfitGalleryItem* LLOutfitGallery::getSelectedItem()
+{
+ return mOutfitMap[mSelectedOutfitUUID];
+}
+
void LLOutfitGallery::buildGalleryPanel(int row_count)
{
LLPanel::Params params;
@@ -608,6 +875,7 @@ void LLOutfitGallery::onChangeOutfitSelection(LLWearableItemsList* list, const L
{
mOutfitMap[category_id]->setSelected(TRUE);
}
+ // mSelectedOutfitUUID will be set in LLOutfitListBase::ChangeOutfitSelection
}
void LLOutfitGallery::wearSelectedOutfit()
@@ -659,7 +927,8 @@ static LLDefaultChildRegistry::Register<LLOutfitGalleryItem> r("outfit_gallery_i
LLOutfitGalleryItem::LLOutfitGalleryItem(const Params& p)
: LLPanel(p),
- mTexturep(NULL),
+ mGallery(nullptr),
+ mTexturep(nullptr),
mSelected(false),
mWorn(false),
mDefaultImage(true),
@@ -764,8 +1033,63 @@ BOOL LLOutfitGalleryItem::handleRightMouseDown(S32 x, S32 y, MASK mask)
BOOL LLOutfitGalleryItem::handleDoubleClick(S32 x, S32 y, MASK mask)
{
+ return openOutfitsContent() || LLPanel::handleDoubleClick(x, y, mask);
+}
+
+BOOL LLOutfitGalleryItem::handleKeyHere(KEY key, MASK mask)
+{
+ if (!mGallery)
+ {
+ return FALSE;
+ }
+
+ BOOL handled = FALSE;
+ switch (key)
+ {
+ case KEY_LEFT:
+ mGallery->moveLeft();
+ handled = true;
+ break;
+
+ case KEY_RIGHT:
+ mGallery->moveRight();
+ handled = true;
+ break;
+
+ case KEY_UP:
+ mGallery->moveUp();
+ handled = true;
+ break;
+
+ case KEY_DOWN:
+ mGallery->moveDown();
+ handled = true;
+ break;
+
+ default:
+ break;
+ }
+ return handled;
+}
+
+void LLOutfitGalleryItem::onFocusLost()
+{
+ setSelected(false);
+
+ LLPanel::onFocusLost();
+}
+
+void LLOutfitGalleryItem::onFocusReceived()
+{
+ setSelected(true);
+
+ LLPanel::onFocusReceived();
+}
+
+bool LLOutfitGalleryItem::openOutfitsContent()
+{
LLTabContainer* appearence_tabs = LLPanelOutfitsInventory::findInstance()->getChild<LLTabContainer>("appearance_tabs");
- if (appearence_tabs && (mUUID != LLUUID()))
+ if (appearence_tabs && mUUID.notNull())
{
appearence_tabs->selectTabByName("outfitslist_tab");
LLPanel* panel = appearence_tabs->getCurrentPanel();
@@ -778,12 +1102,11 @@ BOOL LLOutfitGalleryItem::handleDoubleClick(S32 x, S32 y, MASK mask)
outfit_list->setSelectedOutfitByUUID(mUUID);
LLAccordionCtrlTab* tab = accordion->getSelectedTab();
tab->showAndFocusHeader();
- return TRUE;
+ return true;
}
}
}
-
- return LLPanel::handleDoubleClick(x, y, mask);
+ return false;
}
bool LLOutfitGalleryItem::setImageAssetId(LLUUID image_asset_id)
@@ -829,7 +1152,7 @@ LLContextMenu* LLOutfitGalleryContextMenu::createMenu()
boost::bind(&LLAppearanceMgr::takeOffOutfit, &LLAppearanceMgr::instance(), selected_id));
registrar.add("Outfit.Edit", boost::bind(editOutfit));
registrar.add("Outfit.Rename", boost::bind(renameOutfit, selected_id));
- registrar.add("Outfit.Delete", boost::bind(&LLOutfitGalleryContextMenu::onRemoveOutfit, this, selected_id));
+ registrar.add("Outfit.Delete", boost::bind(LLOutfitGallery::onRemoveOutfit, selected_id));
registrar.add("Outfit.Create", boost::bind(&LLOutfitGalleryContextMenu::onCreate, this, _2));
registrar.add("Outfit.Thumbnail", boost::bind(&LLOutfitGalleryContextMenu::onThumbnail, this, selected_id));
enable_registrar.add("Outfit.OnEnable", boost::bind(&LLOutfitGalleryContextMenu::onEnable, this, _2));
@@ -848,22 +1171,6 @@ void LLOutfitGalleryContextMenu::onThumbnail(const LLUUID& outfit_cat_id)
}
}
-void LLOutfitGalleryContextMenu::onRemoveOutfit(const LLUUID& outfit_cat_id)
-{
- LLNotificationsUtil::add("DeleteOutfits", LLSD(), LLSD(), boost::bind(&LLOutfitGalleryContextMenu::onOutfitsRemovalConfirmation, this, _1, _2, outfit_cat_id));
-}
-
-void LLOutfitGalleryContextMenu::onOutfitsRemovalConfirmation(const LLSD& notification, const LLSD& response, const LLUUID& outfit_cat_id)
-{
- S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
- if (option != 0) return; // canceled
-
- if (outfit_cat_id.notNull())
- {
- gInventory.removeCategory(outfit_cat_id);
- }
-}
-
void LLOutfitGalleryContextMenu::onCreate(const LLSD& data)
{
LLWearableType::EType type = LLWearableType::getInstance()->typeNameToType(data.asString());
diff --git a/indra/newview/lloutfitgallery.h b/indra/newview/lloutfitgallery.h
index 16116d4b71..9915752962 100644
--- a/indra/newview/lloutfitgallery.h
+++ b/indra/newview/lloutfitgallery.h
@@ -74,6 +74,18 @@ public:
/*virtual*/ BOOL postBuild();
/*virtual*/ void onOpen(const LLSD& info);
/*virtual*/ void draw();
+ /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask);
+ void moveUp();
+ void moveDown();
+ void moveLeft();
+ void moveRight();
+
+ /*virtual*/ void onFocusLost();
+ /*virtual*/ void onFocusReceived();
+
+ static void onRemoveOutfit(const LLUUID& outfit_cat_id);
+ static void onOutfitsRemovalConfirmation(const LLSD& notification, const LLSD& response, const LLUUID& outfit_cat_id);
+ void scrollToShowItem(const LLUUID& item_id);
void wearSelectedOutfit();
@@ -108,8 +120,6 @@ protected:
void applyFilter(LLOutfitGalleryItem* item, const std::string& filter_substring);
private:
- void uploadPhoto(LLUUID outfit_id);
- void uploadOutfitImage(const std::vector<std::string>& filenames, LLUUID outfit_id);
LLUUID getPhotoAssetId(const LLUUID& outfit_id);
LLUUID getDefaultPhoto();
void addToGallery(LLOutfitGalleryItem* item);
@@ -127,6 +137,7 @@ private:
void updateGalleryWidth();
LLOutfitGalleryItem* buildGalleryItem(std::string name, LLUUID outfit_id);
+ LLOutfitGalleryItem* getSelectedItem();
void onTextureSelectionChanged(LLInventoryItem* itemp);
@@ -170,9 +181,10 @@ private:
typedef std::map<LLUUID, LLOutfitGalleryItem*> outfit_map_t;
typedef outfit_map_t::value_type outfit_map_value_t;
outfit_map_t mOutfitMap;
- typedef std::map<LLOutfitGalleryItem*, int> item_num_map_t;
+ typedef std::map<LLOutfitGalleryItem*, S32> item_num_map_t;
typedef item_num_map_t::value_type item_numb_map_value_t;
item_num_map_t mItemIndexMap;
+ std::map<S32, LLOutfitGalleryItem*> mIndexToItemMap;
LLInventoryCategoriesObserver* mOutfitsObserver;
@@ -185,14 +197,13 @@ public:
LLOutfitGalleryContextMenu(LLOutfitListBase* outfit_list)
: LLOutfitContextMenu(outfit_list),
mOutfitList(outfit_list){}
+
protected:
/* virtual */ LLContextMenu* createMenu();
bool onEnable(LLSD::String param);
bool onVisible(LLSD::String param);
void onThumbnail(const LLUUID& outfit_cat_id);
void onCreate(const LLSD& data);
- void onRemoveOutfit(const LLUUID& outfit_cat_id);
- void onOutfitsRemovalConfirmation(const LLSD& notification, const LLSD& response, const LLUUID& outfit_cat_id);
private:
LLOutfitListBase* mOutfitList;
};
@@ -226,14 +237,21 @@ public:
/*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask);
/*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask);
/*virtual*/ BOOL handleDoubleClick(S32 x, S32 y, MASK mask);
+ /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask);
+ /*virtual*/ void onFocusLost();
+ /*virtual*/ void onFocusReceived();
+
+ bool openOutfitsContent();
+ void setGallery(LLOutfitGallery* gallery) { mGallery = gallery; }
void setDefaultImage();
bool setImageAssetId(LLUUID asset_id);
LLUUID getImageAssetId();
void setOutfitName(std::string name);
void setOutfitWorn(bool value);
void setSelected(bool value);
- void setUUID(LLUUID outfit_id) {mUUID = outfit_id;}
+ void setUUID(const LLUUID &outfit_id) {mUUID = outfit_id;}
+ LLUUID getUUID() const { return mUUID; }
std::string getItemName() {return mOutfitName;}
bool isDefaultImage() {return mDefaultImage;}
@@ -242,6 +260,7 @@ public:
void setHidden(bool hidden) {mHidden = hidden;}
private:
+ LLOutfitGallery* mGallery;
LLPointer<LLViewerFetchedTexture> mTexturep;
LLUUID mUUID;
LLUUID mImageAssetId;
diff --git a/indra/newview/skins/default/xui/en/panel_outfit_gallery.xml b/indra/newview/skins/default/xui/en/panel_outfit_gallery.xml
index e3790ae09b..e951d25391 100644
--- a/indra/newview/skins/default/xui/en/panel_outfit_gallery.xml
+++ b/indra/newview/skins/default/xui/en/panel_outfit_gallery.xml
@@ -40,50 +40,10 @@
layout="topleft"
left="4"
top="0"
+ tab_stop="true"
name="gallery_scroll_panel"
opaque="false"
top_pad="0">
- <!--outfit_gallery_item
- layout="topleft"
- left="10"
- name="preview_outfit1"
- height="175"
- width="150"
- follows="left|top"/-->
- <!--layout_stack follows="left|right" height="180" width="498" layout="topleft" left="0" animate="false" top="0" name="top_gallery_stack" orientation="horizontal">
- <layout_panel follows="left|top" height="175" width="166" layout="topleft" left="0" top="0" auto_resize="false" visible="true" name="top_gallery_panel">
- <outfit_gallery_item layout="topleft" left="10" name="preview_outfit1" height="175" width="150" follows="left|top"/>
- </layout_panel>
- <layout_panel follows="left|top" height="175" width="166" layout="topleft" left="0" top="0" auto_resize="false" visible="true" name="top panel">
- <outfit_gallery_item layout="topleft" left="10" name="preview_outfit2" height="175" width="150" follows="left|top"/>
- </layout_panel>
- <layout_panel follows="left|top" height="175" width="166" layout="topleft" left="0" top="0" auto_resize="false" visible="true" name="top panel">
- <outfit_gallery_item layout="topleft" left="10" name="preview_outfit2" height="175" width="150" follows="left|top"/>
- </layout_panel>
- </layout_stack>
- <layout_stack follows="left|right" height="180" width="498" layout="topleft" left="0" animate="false" top="190" name="top_gallery_stack" orientation="horizontal">
- <layout_panel follows="left|top" height="175" width="166" layout="topleft" left="0" top="0" auto_resize="false" visible="true" name="top_gallery_panel">
- <outfit_gallery_item layout="topleft" left="10" name="preview_outfit1" height="175" width="150" follows="left|top"/>
- </layout_panel>
- <layout_panel follows="left|top" height="175" width="166" layout="topleft" left="0" top="0" auto_resize="false" visible="true" name="top panel">
- <outfit_gallery_item layout="topleft" left="10" name="preview_outfit2" height="175" width="150" follows="left|top"/>
- </layout_panel>
- <layout_panel follows="left|top" height="175" width="166" layout="topleft" left="0" top="0" auto_resize="false" visible="true" name="top panel">
- <outfit_gallery_item layout="topleft" left="10" name="preview_outfit2" height="175" width="150" follows="left|top"/>
- </layout_panel>
- </layout_stack>
- <layout_stack follows="left|right" height="180" width="498" layout="topleft" left="0" animate="false" top="380" name="top_gallery_stack" orientation="horizontal">
- <layout_panel follows="left|top" height="175" width="166" layout="topleft" left="0" top="0" auto_resize="false" visible="true" name="top_gallery_panel">
- <outfit_gallery_item layout="topleft" left="10" name="preview_outfit1" height="175" width="150" follows="left|top"/>
- </layout_panel>
- <layout_panel follows="left|top" height="175" width="166" layout="topleft" left="0" top="0" auto_resize="false" visible="true" name="top panel">
- <outfit_gallery_item layout="topleft" left="10" name="preview_outfit2" height="175" width="150" follows="left|top"/>
- </layout_panel>
- <layout_panel follows="left|top" height="175" width="166" layout="topleft" left="0" top="0" auto_resize="false" visible="true" name="top panel">
- <outfit_gallery_item layout="topleft" left="10" name="preview_outfit2" height="175" width="150" follows="left|top"/>
- </layout_panel>
- </layout_stack-->
- <!--</panel>-->
</scroll_container>
<panel
background_visible="true"