diff --git a/indra/llui/llcombobox.cpp b/indra/llui/llcombobox.cpp index 25331dadef..e99d9d6522 100644 --- a/indra/llui/llcombobox.cpp +++ b/indra/llui/llcombobox.cpp @@ -173,9 +173,9 @@ void LLComboBox::initFromParams(const LLComboBox::Params& p) // virtual bool LLComboBox::postBuild() { - if (mControlVariable) + if (getControlVariable()) { - setValue(mControlVariable->getValue()); // selects the appropriate item + setValue(getControlVariable()->getValue()); // selects the appropriate item } return true; } @@ -278,9 +278,9 @@ LLScrollListItem* LLComboBox::add(const std::string& name, EAddPosition pos, boo item->setEnabled(enabled); if (!mAllowTextEntry && mLabel.empty()) { - if (mControlVariable) + if (getControlVariable()) { - setValue(mControlVariable->getValue()); // selects the appropriate item + setValue(getControlVariable()->getValue()); // selects the appropriate item } else { @@ -297,9 +297,9 @@ LLScrollListItem* LLComboBox::add(const std::string& name, const LLUUID& id, EAd item->setEnabled(enabled); if (!mAllowTextEntry && mLabel.empty()) { - if (mControlVariable) + if (getControlVariable()) { - setValue(mControlVariable->getValue()); // selects the appropriate item + setValue(getControlVariable()->getValue()); // selects the appropriate item } else { @@ -317,9 +317,9 @@ LLScrollListItem* LLComboBox::add(const std::string& name, void* userdata, EAddP item->setUserdata( userdata ); if (!mAllowTextEntry && mLabel.empty()) { - if (mControlVariable) + if (getControlVariable()) { - setValue(mControlVariable->getValue()); // selects the appropriate item + setValue(getControlVariable()->getValue()); // selects the appropriate item } else { @@ -336,9 +336,9 @@ LLScrollListItem* LLComboBox::add(const std::string& name, LLSD value, EAddPosit item->setEnabled(enabled); if (!mAllowTextEntry && mLabel.empty()) { - if (mControlVariable) + if (getControlVariable()) { - setValue(mControlVariable->getValue()); // selects the appropriate item + setValue(getControlVariable()->getValue()); // selects the appropriate item } else { diff --git a/indra/llui/llfolderview.cpp b/indra/llui/llfolderview.cpp index 10985b4728..aa31b64b8a 100644 --- a/indra/llui/llfolderview.cpp +++ b/indra/llui/llfolderview.cpp @@ -207,7 +207,7 @@ LLFolderView::LLFolderView(const Params& p) mAutoOpenCandidate = NULL; mAutoOpenTimer.stop(); mKeyboardSelection = false; - mIndentation = getParentFolder() ? getParentFolder()->getIndentation() + mLocalIndentation : 0; + mIndentation = getParentFolder() ? getParentFolder()->getIndentation() + mStyle->localIndentation : 0; //clear label // go ahead and render root folder as usual @@ -230,11 +230,11 @@ LLFolderView::LLFolderView(const Params& p) // Textbox LLTextBox::Params text_p; LLFontGL* font = getLabelFontForStyle(mLabelStyle); - //mIconPad, mTextPad are set in folder_view_item.xml - LLRect new_r = LLRect(rect.mLeft + mIconPad, - rect.mTop - mTextPad, + //icon_pad, text_pad are set in folder_view_item.xml (shared via mStyle) + LLRect new_r = LLRect(rect.mLeft + mStyle->iconPad, + rect.mTop - mStyle->textPad, rect.mRight, - rect.mTop - mTextPad - font->getLineHeight()); + rect.mTop - mStyle->textPad - font->getLineHeight()); text_p.rect(new_r); text_p.name(std::string(p.name)); text_p.font(font); diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp index fcc1964bd6..0bf4a5fa8d 100644 --- a/indra/llui/llfolderviewitem.cpp +++ b/indra/llui/llfolderviewitem.cpp @@ -167,6 +167,51 @@ LLFolderViewItem::Params::Params() max_folder_item_overlap("max_folder_item_overlap", 0) { } +//static +const LLFolderViewItemStyle* LLFolderViewItem::internStyle(const Params& p) +{ + // These come from widget params and never change per item, so we share one + // immutable instance across every item built from the same params. There are + // only a few distinct param sets (inventory items/folders, conversation view, + // etc.), so a linear scan is cheap even when building 100k+ items. + // Main-thread only (UI construction); no synchronization needed. + static std::vector> sStyles; + + LLFolderViewItemStyle candidate; + candidate.fontColor = p.font_color; + candidate.fontHighlightColor = p.font_highlight_color; + candidate.itemHeight = p.item_height; + candidate.localIndentation = p.folder_indentation; + candidate.iconPad = p.icon_pad; + candidate.iconWidth = p.icon_width; + candidate.textPad = p.text_pad; + candidate.textPadTop = p.text_pad_top; + candidate.arrowSize = p.arrow_size; + candidate.arrowPadTop = p.arrow_pad_top; + candidate.maxFolderItemOverlap = p.max_folder_item_overlap; + + for (const std::unique_ptr& style : sStyles) + { + if (style->itemHeight == candidate.itemHeight + && style->localIndentation == candidate.localIndentation + && style->iconPad == candidate.iconPad + && style->iconWidth == candidate.iconWidth + && style->textPad == candidate.textPad + && style->textPadTop == candidate.textPadTop + && style->arrowSize == candidate.arrowSize + && style->arrowPadTop == candidate.arrowPadTop + && style->maxFolderItemOverlap == candidate.maxFolderItemOverlap + && LLInitParam::ParamCompare::equals(style->fontColor, candidate.fontColor) + && LLInitParam::ParamCompare::equals(style->fontHighlightColor, candidate.fontHighlightColor)) + { + return style.get(); + } + } + + sStyles.push_back(std::make_unique(candidate)); + return sStyles.back().get(); +} + // Default constructor LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p) : LLView(p), @@ -185,9 +230,8 @@ LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p) mLabelStyle( LLFontGL::NORMAL ), pLabelFont(nullptr), mHasVisibleChildren(false), - mLocalIndentation(p.folder_indentation), + mStyle(internStyle(p)), mIndentation(0), - mItemHeight(p.item_height), mControlLabelRotation(0.f), mDragAndDropTarget(false), mLabel(utf8str_to_wstring(p.name)), @@ -196,18 +240,7 @@ LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p) mIsMouseOverTitle(false), mMarketplaceItem(p.marketplace_item), mAllowDrop(p.allow_drop), - mFontColor(p.font_color), - mFontHighlightColor(p.font_highlight_color), - mLeftPad(p.left_pad), - mIconPad(p.icon_pad), - mIconWidth(p.icon_width), - mTextPad(p.text_pad), - mTextPadRight(p.text_pad_right), - mTextPadTop(p.text_pad_top), - mArrowSize(p.arrow_size), - mArrowPadTop(p.arrow_pad_top), mSingleFolderMode(p.single_folder_mode), - mMaxFolderItemOverlap(p.max_folder_item_overlap), mDoubleClickOverride(p.double_click_override) { if (!sColorSetInitialized) @@ -361,7 +394,10 @@ void LLFolderViewItem::refresh() LLFolderViewModelItem& vmi = *getViewModelItem(); mLabel = utf8str_to_wstring(vmi.getDisplayName()); - mLabelFontBuffer.reset(); + if (mLabelFontBuffer) + { + mLabelFontBuffer->reset(); + } mIsFavorite = vmi.isFavorite() && !vmi.isItemInTrash(); setToolTip(vmi.getName()); // icons are slightly expensive to get, can be optimized @@ -377,7 +413,12 @@ void LLFolderViewItem::refresh() mLabelStyle = vmi.getLabelStyle(); pLabelFont = nullptr; // refresh can be called from a coro, don't use getLabelFontForStyle, coro trips font list tread safety mLabelSuffix = utf8str_to_wstring(vmi.getLabelSuffix()); - mSuffixFontBuffer.reset(); + // Only invalidate cached geometry if the buffer exists; if it doesn't, + // draw() will lazily create it. Don't allocate here just to reset it. + if (mSuffixFontBuffer) + { + mSuffixFontBuffer->reset(); + } } // Dirty the filter flag of the model from the view (CHUI-849) @@ -406,6 +447,13 @@ void LLFolderViewItem::refreshSuffix() mLabelStyle = vmi->getLabelStyle(); pLabelFont = nullptr; mLabelSuffix = utf8str_to_wstring(vmi->getLabelSuffix()); + // LLFontVertexBuffer::render doesn't compare the string, so cached + // geometry must be invalidated here or it would replay the old suffix. + // (A style change is covered by render's font-pointer compare.) + if (mSuffixFontBuffer) + { + mSuffixFontBuffer->reset(); + } } mLabelWidthDirty = true; @@ -445,7 +493,7 @@ void LLFolderViewItem::addToFolder(LLFolderViewFolder* folder) // Compute indentation since parent folder changed mIndentation = (getParentFolder()) - ? getParentFolder()->getIndentation() + mLocalIndentation + ? getParentFolder()->getIndentation() + mStyle->localIndentation : 0; } @@ -456,7 +504,7 @@ S32 LLFolderViewItem::arrange( S32* width, S32* height ) { // Only indent deeper items in hierarchy mIndentation = (getParentFolder()) - ? getParentFolder()->getIndentation() + mLocalIndentation + ? getParentFolder()->getIndentation() + mStyle->localIndentation : 0; if (mLabelWidthDirty) { @@ -466,7 +514,9 @@ S32 LLFolderViewItem::arrange( S32* width, S32* height ) // it is purely visual, so it is fine to do at our laisure refreshSuffix(); } - mLabelWidth = getLabelXPos() + getLabelFontForStyle(mLabelStyle)->getWidth(mLabel.c_str()) + getLabelFontForStyle(LLFontGL::NORMAL)->getWidth(mLabelSuffix.c_str()) + mLabelPaddingRight; + // getLabelFont() is the cached font for mLabelStyle; sSuffixFont is the cached NORMAL-style + // font. Both avoid the per-call sFonts map lookup that getLabelFontForStyle() does. + mLabelWidth = getLabelXPos() + getLabelFont()->getWidth(mLabel.c_str()) + sSuffixFont->getWidth(mLabelSuffix.c_str()) + mLabelPaddingRight; mLabelWidthDirty = false; if (mIsFavorite) { @@ -489,22 +539,22 @@ S32 LLFolderViewItem::arrange( S32* width, S32* height ) S32 LLFolderViewItem::getItemHeight() const { - return mItemHeight; + return mStyle->itemHeight; } S32 LLFolderViewItem::getLabelXPos() { - return getIndentation() + mArrowSize + mTextPad + mIconWidth + mIconPad; + return getIndentation() + mStyle->arrowSize + mStyle->textPad + mStyle->iconWidth + mStyle->iconPad; } S32 LLFolderViewItem::getIconPad() { - return mIconPad; + return mStyle->iconPad; } S32 LLFolderViewItem::getTextPad() { - return mTextPad; + return mStyle->textPad; } // *TODO: This can be optimized a lot by simply recording that it is @@ -675,7 +725,7 @@ bool LLFolderViewItem::handleMouseDown( S32 x, S32 y, MASK mask ) bool LLFolderViewItem::handleHover( S32 x, S32 y, MASK mask ) { - mIsMouseOverTitle = (y > (getRect().getHeight() - mItemHeight)); + mIsMouseOverTitle = (y > (getRect().getHeight() - mStyle->itemHeight)); if( hasMouseCapture() && isMovable() ) { @@ -815,8 +865,8 @@ void LLFolderViewItem::drawOpenFolderArrow() if (hasVisibleChildren() || !isFolderComplete()) { gl_draw_scaled_rotated_image( - mIndentation, getRect().getHeight() - mArrowSize - mArrowPadTop - sTopPad, - mArrowSize, mArrowSize, mControlLabelRotation, sFolderArrowImg->getImage(), sFgColor); + mIndentation, getRect().getHeight() - mStyle->arrowSize - mStyle->arrowPadTop - sTopPad, + mStyle->arrowSize, mStyle->arrowSize, mControlLabelRotation, sFolderArrowImg->getImage(), sFgColor); } } @@ -851,7 +901,7 @@ void LLFolderViewItem::drawFavoriteIcon() } gl_draw_scaled_image( x_offset - FAVORITE_IMAGE_SIZE - FAVORITE_IMAGE_PAD, - getRect().getHeight() - mItemHeight + FAVORITE_IMAGE_PAD, + getRect().getHeight() - mStyle->itemHeight + FAVORITE_IMAGE_PAD, FAVORITE_IMAGE_SIZE, FAVORITE_IMAGE_SIZE, favorite_image->getImage(), @@ -887,8 +937,8 @@ void LLFolderViewItem::drawHighlight(bool showContent, bool hasKeyboardFocus, const LLUIColor& focusOutlineColor, const LLUIColor& mouseOverColor) { const S32 focus_top = getRect().getHeight(); - const S32 focus_bottom = getRect().getHeight() - mItemHeight; - const bool folder_open = (getRect().getHeight() > mItemHeight + 4); + const S32 focus_bottom = getRect().getHeight() - mStyle->itemHeight; + const bool folder_open = (getRect().getHeight() > mStyle->itemHeight + 4); const S32 FOCUS_LEFT = 1; // Determine which background color to use for highlighting @@ -992,12 +1042,30 @@ void LLFolderViewItem::drawHighlight(bool showContent, bool hasKeyboardFocus, } } +void LLFolderViewItem::setVisible(bool visible) +{ + if (!visible) + { + // Off-screen items don't need cached text geometry; release it (and the + // buffer objects themselves). Rebuilt lazily by drawLabel()/draw() if the + // item is shown again. Resetting an already-null buffer is a no-op, so the + // repeated setVisible(false) calls during folder open/close animation are cheap. + mLabelFontBuffer.reset(); + mSuffixFontBuffer.reset(); + } + LLView::setVisible(visible); +} + void LLFolderViewItem::drawLabel(const LLFontGL * font, const F32 x, const F32 y, const LLColor4& color, F32 &right_x) { //--------------------------------------------------------------------------------// // Draw the actual label text // - mLabelFontBuffer.render(font, mLabel, 0, x, y, color, + if (!mLabelFontBuffer) + { + mLabelFontBuffer = std::make_unique(); + } + mLabelFontBuffer->render(font, mLabel, 0, x, y, color, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, getRect().getWidth() - (S32) x - mLabelPaddingRight, &right_x, /*use_ellipses*/true); } @@ -1023,7 +1091,7 @@ void LLFolderViewItem::draw() //--------------------------------------------------------------------------------// // Draw open icon // - const S32 icon_x = mIndentation + mArrowSize + mTextPad; + const S32 icon_x = mIndentation + mStyle->arrowSize + mStyle->textPad; const S32 rect_height = getRect().getHeight(); if (!mIconOpen.isNull() && (llabs(mControlLabelRotation) > 80)) // For open folders { @@ -1036,7 +1104,10 @@ void LLFolderViewItem::draw() if (mIconOverlay && getRoot()->showItemLinkOverlays()) { - mIconOverlay->draw(icon_x, rect_height - mIcon->getHeight() - sTopPad + 1); + // mIcon may be null (see the 'else if (mIcon)' above); fall back to the + // overlay's own height rather than dereferencing a null icon. + S32 overlay_icon_height = mIcon ? mIcon->getHeight() : mIconOverlay->getHeight(); + mIconOverlay->draw(icon_x, rect_height - overlay_icon_height - sTopPad + 1); } //--------------------------------------------------------------------------------// @@ -1049,7 +1120,7 @@ void LLFolderViewItem::draw() S32 filter_string_length = mViewModelItem->hasFilterStringMatch() ? (S32)mViewModelItem->getFilterStringSize() : 0; F32 right_x = 0; - F32 y = (F32)rect_height - line_height - (F32)mTextPadTop - (F32)sTopPad; + F32 y = (F32)rect_height - line_height - (F32)mStyle->textPadTop - (F32)sTopPad; F32 text_left = (F32)getLabelXPos(); LLWString combined_string = mLabel + mLabelSuffix; @@ -1092,7 +1163,7 @@ void LLFolderViewItem::draw() LLColor4 color; if (mIsSelected && filled) { - color = mFontHighlightColor; + color = mStyle->fontHighlightColor; } else if (mIsFavorite && highlight_color) { @@ -1100,7 +1171,7 @@ void LLFolderViewItem::draw() } else { - color = mFontColor; + color = mStyle->fontColor; } if (isFadeItem()) @@ -1115,7 +1186,11 @@ void LLFolderViewItem::draw() // if (!mLabelSuffix.empty()) { - mSuffixFontBuffer.render(sSuffixFont, mLabelSuffix, 0, right_x, y, isFadeItem() ? color : sSuffixColor.get(), + if (!mSuffixFontBuffer) + { + mSuffixFontBuffer = std::make_unique(); + } + mSuffixFontBuffer->render(sSuffixFont, mLabelSuffix, 0, right_x, y, isFadeItem() ? color : sSuffixColor.get(), LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, &right_x); } @@ -1128,7 +1203,7 @@ void LLFolderViewItem::draw() if(mLabelSuffix.empty() || (font == sSuffixFont)) { F32 match_string_left = text_left + font->getWidthF32(combined_string.c_str(), 0, filter_offset + filter_string_length) - font->getWidthF32(combined_string.c_str(), filter_offset, filter_string_length); - F32 yy = (F32)rect_height - line_height - (F32)mTextPadTop - (F32)sTopPad; + F32 yy = (F32)rect_height - line_height - (F32)mStyle->textPadTop - (F32)sTopPad; font->render(combined_string, filter_offset, match_string_left, yy, sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, filter_string_length, S32_MAX, &right_x); @@ -1139,7 +1214,7 @@ void LLFolderViewItem::draw() if(label_filter_length > 0) { F32 match_string_left = text_left + font->getWidthF32(mLabel.c_str(), 0, filter_offset + label_filter_length) - font->getWidthF32(mLabel.c_str(), filter_offset, label_filter_length); - F32 yy = (F32)rect_height - line_height - (F32)mTextPadTop - (F32)sTopPad; + F32 yy = (F32)rect_height - line_height - (F32)mStyle->textPadTop - (F32)sTopPad; font->render(mLabel, filter_offset, match_string_left, yy, sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, label_filter_length, S32_MAX, &right_x); @@ -1150,7 +1225,7 @@ void LLFolderViewItem::draw() { S32 suffix_offset = llmax(0, filter_offset - (S32)mLabel.size()); F32 match_string_left = text_left + font->getWidthF32(mLabel.c_str(), 0, static_cast(mLabel.size())) + sSuffixFont->getWidthF32(mLabelSuffix.c_str(), 0, suffix_offset + suffix_filter_length) - sSuffixFont->getWidthF32(mLabelSuffix.c_str(), suffix_offset, suffix_filter_length); - F32 yy = (F32)rect_height - sSuffixFont->getLineHeight() - (F32)mTextPadTop - (F32)sTopPad; + F32 yy = (F32)rect_height - sSuffixFont->getLineHeight() - (F32)mStyle->textPadTop - (F32)sTopPad; sSuffixFont->render(mLabelSuffix, suffix_offset, match_string_left, yy, sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, suffix_filter_length, S32_MAX, &right_x); @@ -1236,7 +1311,7 @@ void LLFolderViewFolder::addToFolder(LLFolderViewFolder* folder) // Compute indentation since parent folder changed mIndentation = (getParentFolder()) - ? getParentFolder()->getIndentation() + mLocalIndentation + ? getParentFolder()->getIndentation() + mStyle->localIndentation : 0; if(isOpen() && folder->isOpen()) @@ -1386,7 +1461,7 @@ S32 LLFolderViewFolder::arrange( S32* width, S32* height ) folders_t::iterator fit = iter++; // number of pixels that bottom of folder label is from top of parent folder if (getRect().getHeight() - (*fit)->getRect().mTop + (*fit)->getItemHeight() - > ll_round(mCurHeight) + mMaxFolderItemOverlap) + > ll_round(mCurHeight) + mStyle->maxFolderItemOverlap) { // hide if beyond current folder height (*fit)->setVisible(false); @@ -1399,7 +1474,7 @@ S32 LLFolderViewFolder::arrange( S32* width, S32* height ) items_t::iterator iit = iter++; // number of pixels that bottom of item label is from top of parent folder if (getRect().getHeight() - (*iit)->getRect().mBottom - > ll_round(mCurHeight) + mMaxFolderItemOverlap) + > ll_round(mCurHeight) + mStyle->maxFolderItemOverlap) { (*iit)->setVisible(false); } @@ -1982,7 +2057,16 @@ void LLFolderViewFolder::onIdleUpdateFavorites(void* data) } else { - // Nothing changed + // Nothing changed. On a later iteration 'self' was already + // flagged FAVORITE_CLEANUP before we advanced, but if we bail + // on the very first iteration (parent == self) it never was, + // so clear it here. Otherwise this idle callback stays + // registered and re-scans every frame, never getting removed. + if (parent == self) + { + self->mFavoritesDirtyFlags = 0; + gIdleCallbacks.deleteFunction(&LLFolderViewFolder::onIdleUpdateFavorites, self); + } break; } } @@ -2317,7 +2401,7 @@ bool LLFolderViewFolder::handleRightMouseDown( S32 x, S32 y, MASK mask ) bool LLFolderViewFolder::handleHover(S32 x, S32 y, MASK mask) { - mIsMouseOverTitle = (y > (getRect().getHeight() - mItemHeight)); + mIsMouseOverTitle = (y > (getRect().getHeight() - mStyle->itemHeight)); bool handled = LLView::handleHover(x, y, mask); @@ -2339,7 +2423,7 @@ bool LLFolderViewFolder::handleMouseDown( S32 x, S32 y, MASK mask ) } if( !handled ) { - if((mIndentation < x && x < mIndentation + (isCollapsed() ? 0 : mArrowSize) + mTextPad) + if((mIndentation < x && x < mIndentation + (isCollapsed() ? 0 : mStyle->arrowSize) + mStyle->textPad) && !mSingleFolderMode) { toggleOpen(); @@ -2398,7 +2482,7 @@ bool LLFolderViewFolder::handleDoubleClick( S32 x, S32 y, MASK mask ) return true; } } - if(mIndentation < x && x < mIndentation + (isCollapsed() ? 0 : mArrowSize) + mTextPad) + if(mIndentation < x && x < mIndentation + (isCollapsed() ? 0 : mStyle->arrowSize) + mStyle->textPad) { // don't select when user double-clicks plus sign // so as not to contradict single-click behavior diff --git a/indra/llui/llfolderviewitem.h b/indra/llui/llfolderviewitem.h index 258a806b91..958a8ab43e 100644 --- a/indra/llui/llfolderviewitem.h +++ b/indra/llui/llfolderviewitem.h @@ -31,6 +31,8 @@ #include "lluiimage.h" #include "llfontvertexbuffer.h" +#include + class LLFolderView; class LLFolderViewModelItem; class LLFolderViewFolder; @@ -38,6 +40,27 @@ class LLFolderViewFunctor; class LLFolderViewFilter; class LLFolderViewModelInterface; +// Per-item layout and colors that come from Params and never vary per item. +// They are identical for every item built from the same widget params, so rather +// than copying ~100 bytes into each of (potentially) hundreds of thousands of +// items, the values are interned (see LLFolderViewItem::internStyle) and shared +// by const pointer. Anything an item mutates per-instance (e.g. indentation, +// label padding) stays a direct member. +struct LLFolderViewItemStyle +{ + LLUIColor fontColor; + LLUIColor fontHighlightColor; + S32 itemHeight{ 0 }; + S32 localIndentation{ 0 }; + S32 iconPad{ 0 }; + S32 iconWidth{ 0 }; + S32 textPad{ 0 }; + S32 textPadTop{ 0 }; + S32 arrowSize{ 0 }; + S32 arrowPadTop{ 0 }; + S32 maxFolderItemOverlap{ 0 }; +}; + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLFolderViewItem // @@ -108,22 +131,12 @@ class LLFolderViewItem : public LLView LLUIImagePtr mIcon, mIconOpen, mIconOverlay; - S32 mLocalIndentation; + // Shared, interned const layout + colors (see LLFolderViewItemStyle). + const LLFolderViewItemStyle* mStyle; S32 mIndentation; - S32 mItemHeight; S32 mDragStartX, mDragStartY; - S32 mLeftPad, - mIconPad, - mIconWidth, - mTextPad, - mTextPadRight, - mTextPadTop, - mArrowSize, - mArrowPadTop, - mMaxFolderItemOverlap; - F32 mControlLabelRotation; LLFolderView* mRoot; bool mHasVisibleChildren, @@ -139,8 +152,6 @@ class LLFolderViewItem : public LLView S32 mCutGeneration; - LLUIColor mFontColor; - LLUIColor mFontHighlightColor; static bool sColorSetInitialized; // For now assuming all colors are the same in derived classes. @@ -308,6 +319,9 @@ class LLFolderViewItem : public LLView //virtual LLView* findChildView(const std::string& name, bool recurse) const { return LLView::findChildView(name, recurse); } + // Releases cached text geometry when hidden so off-screen items hold no font buffers. + virtual void setVisible(bool visible); + // virtual void handleDropped(); virtual void draw(); void drawOpenFolderArrow(); @@ -329,8 +343,16 @@ class LLFolderViewItem : public LLView static LLUIImagePtr sFavoriteContentImg; static LLFontGL* sSuffixFont; - LLFontVertexBuffer mLabelFontBuffer; - LLFontVertexBuffer mSuffixFontBuffer; + // Returns a shared, immutable style for the given params, deduplicated across + // all items (there are only a handful of distinct param sets). Main-thread only. + static const LLFolderViewItemStyle* internStyle(const Params& p); + + // Cached text geometry. Lazily (re)built during draw() and released when the + // item goes off-screen (see setVisible), so only on-screen items hold vertex + // buffers and their MSVC per-std::list sentinel allocations. Null until first + // drawn, after being hidden, or (for the suffix) when there is no suffix. + std::unique_ptr mLabelFontBuffer; + std::unique_ptr mSuffixFontBuffer; LLFontGL* pLabelFont{nullptr}; }; diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index 5764ee735e..48d8559955 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -838,12 +838,12 @@ void LLMenuItemCallGL::updateEnabled( void ) if (mEnableSignal.num_slots() > 0) { bool enabled = mEnableSignal(this, LLSD()); - if (mEnabledControlVariable) + if (getEnabledControlVariable()) { if (!enabled) { // callback overrides control variable; this will call setEnabled() - mEnabledControlVariable->set(false); + getEnabledControlVariable()->set(false); } } else @@ -947,7 +947,7 @@ void LLMenuItemCheckGL::buildDrawLabel( void ) { // Note: mCheckSignal() returns true if no callbacks are set bool checked = mCheckSignal(this, LLSD()); - if (mControlVariable) + if (getControlVariable()) { if (!checked) setControlValue(false); // callback overrides control variable; this will call setValue() diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp index 80c6b9dab5..1a27ff5cf1 100644 --- a/indra/llui/lluictrl.cpp +++ b/indra/llui/lluictrl.cpp @@ -104,11 +104,6 @@ LLUICtrl::LLUICtrl(const LLUICtrl::Params& p, const LLViewModelPtr& viewmodel) mTabStop(false), mTentative(false), mViewModel(viewmodel), - mControlVariable(NULL), - mEnabledControlVariable(NULL), - mDisabledControlVariable(NULL), - mMakeVisibleControlVariable(NULL), - mMakeInvisibleControlVariable(NULL), mCommitSignal(NULL), mValidateSignal(NULL), mMouseEnterSignal(NULL), @@ -258,6 +253,9 @@ LLUICtrl::~LLUICtrl() delete mRightMouseDownSignal; delete mRightMouseUpSignal; delete mDoubleClickSignal; + + // scoped_connection members disconnect from any bound control variables here + delete mControlVariables; } void default_commit_handler(LLUICtrl* ctrl, const LLSD& param) @@ -500,11 +498,20 @@ bool LLUICtrl::postBuild() return LLView::postBuild(); } +LLUICtrl::ControlVariables& LLUICtrl::getControlVars() +{ + if (!mControlVariables) + { + mControlVariables = new ControlVariables(); + } + return *mControlVariables; +} + bool LLUICtrl::setControlValue(const LLSD& value) { - if (mControlVariable) + if (LLControlVariable* control = getControlVariable()) { - mControlVariable->set(value); + control->set(value); return true; } return false; @@ -512,28 +519,29 @@ bool LLUICtrl::setControlValue(const LLSD& value) void LLUICtrl::setControlVariable(LLControlVariable* control) { - if (mControlVariable) + if (mControlVariables && mControlVariables->mControlVariable) { //RN: this will happen in practice, should we try to avoid it? //LL_WARNS() << "setControlName called twice on same control!" << LL_ENDL; - mControlConnection.disconnect(); // disconnect current signal - mControlVariable = NULL; + mControlVariables->mControlConnection.disconnect(); // disconnect current signal + mControlVariables->mControlVariable = NULL; } if (control) { - mControlVariable = control; - mControlConnection = mControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("value"))); - setValue(mControlVariable->getValue()); + ControlVariables& vars = getControlVars(); + vars.mControlVariable = control; + vars.mControlConnection = control->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("value"))); + setValue(control->getValue()); } } void LLUICtrl::removeControlVariable() { - if (mControlVariable) + if (mControlVariables && mControlVariables->mControlVariable) { - mControlConnection.disconnect(); - mControlVariable = NULL; + mControlVariables->mControlConnection.disconnect(); + mControlVariables->mControlVariable = NULL; } } @@ -560,61 +568,65 @@ void LLUICtrl::setControlName(const std::string& control_name, LLView *context) void LLUICtrl::setEnabledControlVariable(LLControlVariable* control) { - if (mEnabledControlVariable) + if (mControlVariables && mControlVariables->mEnabledControlVariable) { - mEnabledControlConnection.disconnect(); // disconnect current signal - mEnabledControlVariable = NULL; + mControlVariables->mEnabledControlConnection.disconnect(); // disconnect current signal + mControlVariables->mEnabledControlVariable = NULL; } if (control) { - mEnabledControlVariable = control; - mEnabledControlConnection = mEnabledControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("enabled"))); - setEnabled(mEnabledControlVariable->getValue().asBoolean()); + ControlVariables& vars = getControlVars(); + vars.mEnabledControlVariable = control; + vars.mEnabledControlConnection = control->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("enabled"))); + setEnabled(control->getValue().asBoolean()); } } void LLUICtrl::setDisabledControlVariable(LLControlVariable* control) { - if (mDisabledControlVariable) + if (mControlVariables && mControlVariables->mDisabledControlVariable) { - mDisabledControlConnection.disconnect(); // disconnect current signal - mDisabledControlVariable = NULL; + mControlVariables->mDisabledControlConnection.disconnect(); // disconnect current signal + mControlVariables->mDisabledControlVariable = NULL; } if (control) { - mDisabledControlVariable = control; - mDisabledControlConnection = mDisabledControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("disabled"))); - setEnabled(!(mDisabledControlVariable->getValue().asBoolean())); + ControlVariables& vars = getControlVars(); + vars.mDisabledControlVariable = control; + vars.mDisabledControlConnection = control->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("disabled"))); + setEnabled(!(control->getValue().asBoolean())); } } void LLUICtrl::setMakeVisibleControlVariable(LLControlVariable* control) { - if (mMakeVisibleControlVariable) + if (mControlVariables && mControlVariables->mMakeVisibleControlVariable) { - mMakeVisibleControlConnection.disconnect(); // disconnect current signal - mMakeVisibleControlVariable = NULL; + mControlVariables->mMakeVisibleControlConnection.disconnect(); // disconnect current signal + mControlVariables->mMakeVisibleControlVariable = NULL; } if (control) { - mMakeVisibleControlVariable = control; - mMakeVisibleControlConnection = mMakeVisibleControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("visible"))); - setVisible(mMakeVisibleControlVariable->getValue().asBoolean()); + ControlVariables& vars = getControlVars(); + vars.mMakeVisibleControlVariable = control; + vars.mMakeVisibleControlConnection = control->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("visible"))); + setVisible(control->getValue().asBoolean()); } } void LLUICtrl::setMakeInvisibleControlVariable(LLControlVariable* control) { - if (mMakeInvisibleControlVariable) + if (mControlVariables && mControlVariables->mMakeInvisibleControlVariable) { - mMakeInvisibleControlConnection.disconnect(); // disconnect current signal - mMakeInvisibleControlVariable = NULL; + mControlVariables->mMakeInvisibleControlConnection.disconnect(); // disconnect current signal + mControlVariables->mMakeInvisibleControlVariable = NULL; } if (control) { - mMakeInvisibleControlVariable = control; - mMakeInvisibleControlConnection = mMakeInvisibleControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("invisible"))); - setVisible(!(mMakeInvisibleControlVariable->getValue().asBoolean())); + ControlVariables& vars = getControlVars(); + vars.mMakeInvisibleControlVariable = control; + vars.mMakeInvisibleControlConnection = control->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("invisible"))); + setVisible(!(control->getValue().asBoolean())); } } diff --git a/indra/llui/lluictrl.h b/indra/llui/lluictrl.h index 749999bbfe..3162f81447 100644 --- a/indra/llui/lluictrl.h +++ b/indra/llui/lluictrl.h @@ -180,7 +180,8 @@ class LLUICtrl virtual void setControlName(const std::string& control, LLView *context = NULL); void removeControlVariable(); - LLControlVariable* getControlVariable() { return mControlVariable; } + LLControlVariable* getControlVariable() { return mControlVariables ? mControlVariables->mControlVariable : nullptr; } + LLControlVariable* getEnabledControlVariable() { return mControlVariables ? mControlVariables->mEnabledControlVariable : nullptr; } void setEnabledControlVariable(LLControlVariable* control); void setDisabledControlVariable(LLControlVariable* control); @@ -305,17 +306,6 @@ class LLUICtrl LLViewModelPtr mViewModel; - LLControlVariable* mControlVariable; - boost::signals2::connection mControlConnection; - LLControlVariable* mEnabledControlVariable; - boost::signals2::connection mEnabledControlConnection; - LLControlVariable* mDisabledControlVariable; - boost::signals2::connection mDisabledControlConnection; - LLControlVariable* mMakeVisibleControlVariable; - boost::signals2::connection mMakeVisibleControlConnection; - LLControlVariable* mMakeInvisibleControlVariable; - boost::signals2::connection mMakeInvisibleControlConnection; - std::string mFunctionName; static F32 sActiveControlTransparency; @@ -331,6 +321,27 @@ class LLUICtrl bool mTentative; ETypeTransparency mTransparencyType; + + // Binding a control to a setting variable is uncommon, so keep the + // pointers and their scoped (auto-disconnecting) signal connections in a + // block allocated only on first use. This both avoids ~120 bytes of + // mostly-unused members on every control and ensures the connections are + // disconnected when the control is destroyed (scoped_connection RAII). + struct ControlVariables + { + LLControlVariable* mControlVariable{ nullptr }; + LLControlVariable* mEnabledControlVariable{ nullptr }; + LLControlVariable* mDisabledControlVariable{ nullptr }; + LLControlVariable* mMakeVisibleControlVariable{ nullptr }; + LLControlVariable* mMakeInvisibleControlVariable{ nullptr }; + boost::signals2::scoped_connection mControlConnection; + boost::signals2::scoped_connection mEnabledControlConnection; + boost::signals2::scoped_connection mDisabledControlConnection; + boost::signals2::scoped_connection mMakeVisibleControlConnection; + boost::signals2::scoped_connection mMakeInvisibleControlConnection; + }; + ControlVariables* mControlVariables{ nullptr }; + ControlVariables& getControlVars(); // lazily allocates mControlVariables }; // Build time optimization, generate once in .cpp file diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp index 7d6c937b85..d896a9ffaf 100644 --- a/indra/llui/llview.cpp +++ b/indra/llui/llview.cpp @@ -156,12 +156,17 @@ LLView::LLView(const LLView::Params& p) mUseBoundingRect(p.use_bounding_rect), mDefaultTabGroup(p.default_tab_group), mLastTabGroup(0), - mToolTipMsg((LLStringExplicit)p.tool_tip()), mDefaultWidgets(NULL) { // create rect first, as this will supply initial follows flags setShape(p.rect); parseFollowsFlags(p); + + // allocate the tooltip holder only when a tooltip is actually provided + if (!p.tool_tip().empty()) + { + mToolTipMsg = new LLUIString((LLStringExplicit)p.tool_tip()); + } } LLView::~LLView() @@ -194,6 +199,8 @@ LLView::~LLView() delete mDefaultWidgets; mDefaultWidgets = NULL; } + + delete mToolTipMsg; } // virtual @@ -208,20 +215,36 @@ bool LLView::isPanel() const return false; } +LLUIString& LLView::getOrCreateToolTipMsg() +{ + if (!mToolTipMsg) + { + mToolTipMsg = new LLUIString(); + } + return *mToolTipMsg; +} + void LLView::setToolTip(const LLStringExplicit& msg) { - mToolTipMsg = msg; + if (mToolTipMsg) + { + *mToolTipMsg = msg; + } + else if (!msg.empty()) + { + mToolTipMsg = new LLUIString(msg); + } } bool LLView::setToolTipArg(const LLStringExplicit& key, const LLStringExplicit& text) { - mToolTipMsg.setArg(key, text); + getOrCreateToolTipMsg().setArg(key, text); return true; } void LLView::setToolTipArgs( const LLStringUtil::format_map_t& args ) { - mToolTipMsg.setArgList(args); + getOrCreateToolTipMsg().setArgList(args); } // virtual @@ -931,7 +954,7 @@ const std::string LLView::getToolTip() const } } - return mToolTipMsg.getString(); + return mToolTipMsg ? mToolTipMsg->getString() : LLStringUtil::null; } bool LLView::handleToolTip(S32 x, S32 y, MASK mask) diff --git a/indra/llui/llview.h b/indra/llui/llview.h index d747ef9555..4e5d0150ea 100644 --- a/indra/llui/llview.h +++ b/indra/llui/llview.h @@ -605,7 +605,7 @@ class LLView bool mEnabled; // Enabled means "accepts input that has an effect on the state of the application." // A disabled view, for example, may still have a scrollbar that responds to mouse events. bool mMouseOpaque; // Opaque views handle all mouse events that are over their rect. - LLUIString mToolTipMsg; // isNull() is true if none. + LLUIString* mToolTipMsg { nullptr }; // allocated lazily; null when no tooltip is set U8 mSoundFlags; bool mFromXUI; @@ -625,6 +625,9 @@ class LLView LLView& getDefaultWidgetContainer() const; + // tooltip holder is allocated on demand; most widgets never set one + LLUIString& getOrCreateToolTipMsg(); + // This allows special mouse-event targeting logic for testing. typedef std::function DrilldownFunc; static DrilldownFunc sDrilldown; diff --git a/indra/newview/llconversationview.cpp b/indra/newview/llconversationview.cpp index 4701aad64a..22188b1f51 100644 --- a/indra/newview/llconversationview.cpp +++ b/indra/newview/llconversationview.cpp @@ -418,7 +418,7 @@ S32 LLConversationViewSession::arrange(S32* width, S32* height) //LLFolderViewFolder::arrange computes value for getIndentation() function below S32 arranged = LLFolderViewFolder::arrange(width, height); - S32 h_pad = mHasArrow ? getIndentation() + mArrowSize : getIndentation(); + S32 h_pad = mHasArrow ? getIndentation() + mStyle->arrowSize : getIndentation(); LLRect rect(mCollapsedMode ? getLocalRect().mLeft : h_pad, getLocalRect().mTop, @@ -454,7 +454,7 @@ void LLConversationViewSession::toggleCollapsedMode(bool is_collapsed) // except for the icon which we display in minimized mode getChild("conversation_item_stack")->setVisible(!mCollapsedMode); - S32 h_pad = mHasArrow ? getIndentation() + mArrowSize : getIndentation(); + S32 h_pad = mHasArrow ? getIndentation() + mStyle->arrowSize : getIndentation(); mItemPanel->translate(mCollapsedMode ? -h_pad : h_pad, 0); } @@ -692,7 +692,7 @@ void LLConversationViewParticipant::draw() const LLFontGL* font = getLabelFontForStyle(mLabelStyle); F32 right_x = 0; - F32 y = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad; + F32 y = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mStyle->textPad; F32 text_left = (F32)getLabelXPos(); LLUIColor* color; @@ -837,7 +837,7 @@ void LLConversationViewParticipant::onMouseLeave(S32 x, S32 y, MASK mask) S32 LLConversationViewParticipant::getLabelXPos() { - return getIndentation() + mAvatarIcon->getRect().getWidth() + mIconPad; + return getIndentation() + mAvatarIcon->getRect().getWidth() + mStyle->iconPad; } // static diff --git a/indra/newview/llinventorybridge.h b/indra/newview/llinventorybridge.h index bb869e0944..c20c430239 100644 --- a/indra/newview/llinventorybridge.h +++ b/indra/newview/llinventorybridge.h @@ -201,7 +201,6 @@ class LLInvFVBridge : public LLFolderViewModelItemInventory const LLUUID mUUID; // item id LLInventoryType::EType mInvType; bool mIsLink; - LLTimer mTimeSinceRequestStart; mutable std::string mDisplayName; mutable std::string mSearchableName;