NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

aurumStoreContainer.py (27233B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\view\aurumstore\aurumStoreContainer.py
      2 import collections
      3 import logging
      4 import math
      5 import carbonui.const as uiconst
      6 import eve.client.script.ui.shared.pointerTool.pointerToolConst as pConst
      7 from carbonui.control.singlelineedits.singleLineEditText import SingleLineEditText
      8 from carbonui.primitives.container import Container
      9 from carbonui.primitives.containerAutoSize import ContainerAutoSize
     10 from carbonui.primitives.fill import Fill
     11 from carbonui.primitives.flowcontainer import FlowContainer
     12 from carbonui.primitives.gradientSprite import GradientSprite
     13 from carbonui.primitives.gridcontainer import GridContainer
     14 from carbonui.primitives.sprite import Sprite
     15 from carbonui.uicore import uicore
     16 from eve.client.script.ui.shared.redeem.redeemPanel import GetOldRedeemPanel
     17 from vgs.client.currency import OFFER_CURRENCY_SORT_ORDER
     18 from eve.common.lib.vgsConst import CATEGORYTAG_GEM
     19 from eve.client.script.ui.plex.textures import PLEX_32_SOLID_YELLOW
     20 from eve.client.script.ui.util.focusUtil import postponeUntilFocus
     21 from eve.client.script.ui.util.uiComponents import RunThreadOnce
     22 from eve.client.script.ui.view.aurumstore.vgsOffer import Ribbon
     23 from eve.client.script.ui.view.aurumstore.vgsOfferFilterBar import VgsOfferFilterBar
     24 from eve.client.script.ui.view.aurumstore.vgsOfferGrid import OfferGrid
     25 from eve.client.script.ui.view.aurumstore.vgsOfferScrollContainer import OfferScrollContainer
     26 from eve.client.script.ui.view.aurumstore.vgsUiConst import BACKGROUND_COLOR, CONTENT_PADDING, MAX_CONTENT_WIDTH, REDEEM_BUTTON_BACKGROUND_COLOR, REDEEM_BUTTON_FILL_COLOR
     27 from eve.client.script.ui.view.aurumstore.vgsUiPrimitives import AurLabelHeader, CategoryButton, CATEGORY_COLOR, ExitButton, GemLabelHeader, LogoHomeButton, SubCategoryButton, TAG_COLOR, VgsFilterCombo
     28 from eve.client.script.ui.view.viewStateConst import ViewState
     29 from fastcheckout.client.purchasepanels.purchaseButton import PurchaseButton
     30 from fastcheckout.const import FROM_NES_HEADER
     31 from localization import GetByLabel
     32 from localization.util import Sort
     33 logger = logging.getLogger(__name__)
     34 HEADER_HEIGHT = 100
     35 HEADER_PADDING = 2
     36 CATEGORIES_HEIGHT = 36
     37 AD_HEIGHT = 256
     38 CAPTION_HEIGHT = 34
     39 CAPTION_OFFSET = 30
     40 SEARCH_BOX_WIDTH = 300
     41 CONTENT_SLIP_UNDER_AREA_OPACITY = 0.8
     42 BG_WIDTH = 2117
     43 BG_HEIGHT = 1200
     44 BUY_CURRENCY_BUTTON_WIDTH = 106
     45 BUY_CURRENCY_BUTTON_HEIGHT = 30
     46 BUY_CURRENCY_BUTTON_LEFT = 6
     47 BUY_CURRENCY_BUTTON_FONTSIZE = 18
     48 BUY_CURRENCY_FONTSIZE = 27
     49 BUY_CURRENCY_ICONSIZE = 32
     50 BUY_CURRENCY_TEXT_MARGIN = 10
     51 BUY_CURRENCY_ICON_MARGIN = 8
     52 SORT_PRICE_ASCENDING = 1
     53 SORT_PRICE_DESCENDING = 2
     54 SORT_NAME_ASCENDING = 3
     55 SORT_NAME_DESCENDING = 4
     56 DEFAULT_SORT_SELECTION = SORT_NAME_ASCENDING
     57 PAGE_HOME = 1
     58 PAGE_CATEGORY = 2
     59 PAGE_SUBCATEGORY = 3
     60 PAGE_SEARCH = 4
     61 ROOT_CATEGORY_SHIP_SKINS = 1
     62 ROOT_CATEGORY_APPAREL = 3
     63 ROOT_CATEGORY_SERVICES = 21
     64 THREAD_KEY_LOAD_PAGE = 'VGS.LoadPage'
     65 KEY_OFFER = 'offer'
     66 KEY_OFFERS = 'offers'
     67 KEY_HAS_QUANTITY = 'hasQuantity'
     68 KEY_QUANTITY = 'quantity'
     69 ProductOffer = collections.namedtuple('ProductOffer', ['url',
     70  'title',
     71  'image',
     72  'cost'])
     73 SubCategory = collections.namedtuple('SubCategory', ['id', 'title'])
     74 
     75 def SortByPrice(offers, ascending):
     76 
     77     def get_sort_order_for_price(offer):
     78         prices = []
     79         for pricing in offer.offerPricings:
     80             sort_order = OFFER_CURRENCY_SORT_ORDER.get(pricing.currency, 0)
     81             prices.append((sort_order, pricing.price))
     82 
     83         return max(prices)
     84 
     85     return sorted(offers, key=get_sort_order_for_price, reverse=not ascending)
     86 
     87 
     88 def SortByName(offers, ascending):
     89     numericGroupData, nameGroupData = GetOffersGroupData(offers)
     90     sortedNumericGroupData = sorted(numericGroupData.keys())
     91     sortedNameGroupData = sorted(nameGroupData.keys())
     92     allSortedOffers = []
     93     for groupName in sortedNumericGroupData:
     94         offerData = []
     95         for offer in numericGroupData[groupName][KEY_OFFERS]:
     96             isBundle = len(offer.productQuantities) > 1
     97             for entry in offer.productQuantities.values():
     98                 offerData.append({KEY_OFFER: offer,
     99                  KEY_QUANTITY: 1 if isBundle else entry.quantity})
    100                 break
    101 
    102         sortedOfferData = sorted(offerData, key=lambda o: o[KEY_QUANTITY])
    103         for data in sortedOfferData:
    104             allSortedOffers.append(data[KEY_OFFER])
    105 
    106     for groupName in sortedNameGroupData:
    107         sortedOfferData = sorted(nameGroupData[groupName][KEY_OFFERS], key=lambda o: o.name)
    108         for data in sortedOfferData:
    109             allSortedOffers.append(data)
    110 
    111     if not ascending:
    112         allSortedOffers.reverse()
    113     return allSortedOffers
    114 
    115 
    116 def GetOffersGroupData(offers):
    117     PRODUCT_IDS = [2113,
    118      4513,
    119      2459,
    120      1600,
    121      3307]
    122     numericGroupData = {}
    123     nameGroupData = {}
    124     for offer in offers:
    125         offerName = offer.name
    126         hasQuantity = False
    127         for productID in offer.productQuantities.keys():
    128             if productID in PRODUCT_IDS:
    129                 offerName = str(PRODUCT_IDS.index(productID))
    130                 hasQuantity = True
    131             break
    132 
    133         if hasQuantity:
    134             if offerName in numericGroupData:
    135                 numericGroupData.get(offerName)[KEY_OFFERS].append(offer)
    136             else:
    137                 numericGroupData[offerName] = {KEY_OFFERS: [offer],
    138                  KEY_HAS_QUANTITY: hasQuantity}
    139         elif offerName in nameGroupData:
    140             nameGroupData.get(offerName)[KEY_OFFERS].append(offer)
    141         else:
    142             nameGroupData[offerName] = {KEY_OFFERS: [offer],
    143              KEY_HAS_QUANTITY: hasQuantity}
    144 
    145     return (numericGroupData, nameGroupData)
    146 
    147 
    148 def GetSortOrder():
    149     return settings.user.ui.Get('VgsOfferSortOrder', DEFAULT_SORT_SELECTION)
    150 
    151 
    152 def SortOffers(offers):
    153     offerOrder = GetSortOrder()
    154     sortedOffers = offers
    155     if offerOrder == SORT_PRICE_DESCENDING:
    156         sortedOffers = SortByPrice(offers, False)
    157     elif offerOrder == SORT_PRICE_ASCENDING:
    158         sortedOffers = SortByPrice(offers, True)
    159     elif offerOrder == SORT_NAME_DESCENDING:
    160         sortedOffers = SortByName(offers, False)
    161     elif offerOrder == SORT_NAME_ASCENDING:
    162         sortedOffers = SortByName(offers, True)
    163     return sortedOffers
    164 
    165 
    166 class Tag:
    167 
    168     def __init__(self, tagId, name):
    169         self.id = tagId
    170         self.name = name
    171 
    172 
    173 class AurumStoreContainer(Container):
    174     default_name = 'AurumStoreContainer'
    175     default_state = uiconst.UI_NORMAL
    176 
    177     def ApplyAttributes(self, attributes):
    178         self.is_ready = False
    179         Container.ApplyAttributes(self, attributes)
    180         self.store = attributes.store
    181         self.tagsByCategoryId = {}
    182         self.selectedRootCategoryId = None
    183         self.selectedCategoryId = None
    184         self.selectedCurrency = None
    185         self.activeTagsByRootCategoryId = {}
    186         self.aurumStoreWindow = None
    187         self.page = None
    188         self.category = None
    189         self.subCategory = None
    190         self.subcategoryButtons = []
    191         self.fastCheckoutService = sm.GetService('fastCheckoutClientService')
    192         self.vgsService = sm.GetService('vgsService')
    193         self.viewStateService = sm.GetService('viewState')
    194         self.featuredOfferIDs = self.vgsService.GetFeaturedOffers()
    195         Fill(bgParent=self, color=(0.0, 0.0, 0.0, 1.0))
    196         self.CreateRedeemingPanelLayout()
    197         self.CreateBaseLayout()
    198         self.CreateHeaderLayout()
    199         self.CreateContentLayout()
    200         self.SetFilterOptions()
    201         self.currentPlexBalance = 0
    202         self.is_ready = True
    203         self._OnResize()
    204 
    205     def _OnResize(self, *args):
    206         if not self.is_ready:
    207             return
    208         self.PositionBackgroundTexture()
    209         top, left, width, height = self.GetAbsolute()
    210         contentWidth = min(MAX_CONTENT_WIDTH, width)
    211         self.leftContainer.width = (width - contentWidth) / 2
    212         self.contentContainer.width = contentWidth
    213         self.rightContainer.width = width - self.leftContainer.width - contentWidth
    214         self.redeemingPanel.width = width
    215         self.SetSidebarContentMask()
    216 
    217     def CreateHeaderLayout(self):
    218         self._AddPlex()
    219         LogoHomeButton(parent=self.headerContainer, align=uiconst.TOPLEFT, onClick=self.OnClickHomeButton)
    220         ExitButton(parent=self.headerContainer, align=uiconst.TOPRIGHT, onClick=lambda : self.exit_vgs(), top=4, left=10, hint=GetByLabel('UI/VirtualGoodsStore/ExitStore'))
    221 
    222     def _AddPlex(self):
    223         plexContainer = Container(name='PlexContainer', parent=self.headerContainer, align=uiconst.TOTOP, height=CAPTION_HEIGHT, top=CAPTION_OFFSET)
    224         buttonContainer = ContainerAutoSize(name='BuyPlexButtonContainer', parent=plexContainer, align=uiconst.TORIGHT, width=BUY_CURRENCY_BUTTON_WIDTH + BUY_CURRENCY_BUTTON_LEFT, left=BUY_CURRENCY_BUTTON_LEFT)
    225         self.buyPlexBtn = PurchaseButton(name='NES_BuyPlexButton', parent=buttonContainer, align=uiconst.CENTERRIGHT, width=BUY_CURRENCY_BUTTON_WIDTH, height=BUY_CURRENCY_BUTTON_HEIGHT, text=GetByLabel('UI/VirtualGoodsStore/BuyAurOnline'), fontsize=BUY_CURRENCY_BUTTON_FONTSIZE, func=self._BuyPlex)
    226         self.aurLabel = AurLabelHeader(name=pConst.UNIQUE_NAME_NEW_EDEN_STORE_PLEX_AMOUNT, parent=plexContainer, align=uiconst.TORIGHT, amount=100, padRight=BUY_CURRENCY_TEXT_MARGIN, shouldUseUnits=False, amountSize=BUY_CURRENCY_FONTSIZE, uniqueUiName=pConst.UNIQUE_NAME_NEW_EDEN_STORE_PLEX_AMOUNT)
    227         iconContainer = Container(name='BuyPlexIconContainer', parent=plexContainer, align=uiconst.TORIGHT, width=BUY_CURRENCY_ICONSIZE)
    228         Sprite(name='PlexIcon', parent=iconContainer, texturePath=PLEX_32_SOLID_YELLOW, width=BUY_CURRENCY_ICONSIZE, height=BUY_CURRENCY_ICONSIZE, align=uiconst.CENTER)
    229         if self.fastCheckoutService.is_china_funnel():
    230             plexContainer.top = 2
    231 
    232     def exit_vgs(self):
    233         if self.aurumStoreWindow:
    234             self.aurumStoreWindow.CloseIfOpen()
    235         uicore.cmd.ToggleAurumStore()
    236 
    237     def _BuyPlex(self):
    238         uicore.cmd.CmdBuyPlex(logContext=FROM_NES_HEADER)
    239 
    240     def _BuyGem(self):
    241         self.SelectCategoryByCategoryTag(CATEGORYTAG_GEM)
    242 
    243     def SetSidebarContentMask(self):
    244         for container in (self.leftContainer, self.rightContainer):
    245             container.Flush()
    246             GradientSprite(name='OfferSlipGradient', align=uiconst.TOTOP, parent=container, rgbData=((0.0, BACKGROUND_COLOR[:3]),), alphaData=((0.0, CONTENT_SLIP_UNDER_AREA_OPACITY), (0.5, 1.0), (1.0, 1.0)), height=self.topContainer.height + self.filterContainer.height + HEADER_PADDING, rotation=math.pi / 2)
    247 
    248     def CreateBaseLayout(self):
    249         self.wholeWidthContainer = Container(parent=self, name='WholeWindowContainer', align=uiconst.TOALL)
    250         self.CreateBackgroundTexture()
    251         self.leftContainer = Container(parent=self.wholeWidthContainer, name='LeftSideBar', align=uiconst.TOLEFT, opacity=0.0)
    252         self.rightContainer = Container(parent=self.wholeWidthContainer, name='RightSideBar', align=uiconst.TORIGHT, opacity=0.0)
    253         self.contentContainer = Container(parent=self.wholeWidthContainer, name='Content', align=uiconst.TOLEFT)
    254         self.topContainer = ContainerAutoSize(name='Top Container', parent=self.contentContainer, align=uiconst.TOTOP)
    255         Fill(name='SlipUnderLayer', bgParent=self.topContainer, color=BACKGROUND_COLOR)
    256         self.headerContainer = Container(parent=self.topContainer, name='Header', align=uiconst.TOTOP, bgColor=(0.0, 0.0, 0.0, 1.0), height=HEADER_HEIGHT, clipChildren=True)
    257         self.categoryContainer = Container(parent=self.topContainer, name='Categories', align=uiconst.TOTOP, height=CATEGORIES_HEIGHT, padTop=HEADER_PADDING, state=uiconst.UI_PICKCHILDREN)
    258         self.subCategoryContainer = Container(name='SubCategories', parent=self.topContainer, align=uiconst.TOTOP, padTop=HEADER_PADDING, bgColor=TAG_COLOR, state=uiconst.UI_PICKCHILDREN, clipChildren=True)
    259         self.subCategoryButtonContainer = FlowContainer(name='SubCategoryButtons', parent=self.subCategoryContainer, centerContent=True, align=uiconst.TOTOP, contentSpacing=(1, 0), state=uiconst.UI_PICKCHILDREN)
    260         self.filterContainer = Container(name='Filter', parent=self.contentContainer, align=uiconst.TOTOP, padTop=HEADER_PADDING, state=uiconst.UI_PICKCHILDREN, height=CATEGORIES_HEIGHT)
    261 
    262     def CreateBackgroundTexture(self):
    263         self.nesBackground = Sprite(parent=self, name='NESBackground', texturePath='res:/UI/Texture/Vgs/background.png', align=uiconst.CENTER, state=uiconst.UI_DISABLED)
    264         self.PositionBackgroundTexture()
    265 
    266     def PositionBackgroundTexture(self):
    267         clientHeight = uicore.desktop.height
    268         percentOfClientHeight = float(clientHeight) / BG_HEIGHT
    269         if clientHeight < BG_HEIGHT:
    270             newHeight = BG_HEIGHT
    271             newWidth = BG_WIDTH
    272         else:
    273             newHeight = clientHeight
    274             newWidth = int(percentOfClientHeight * BG_WIDTH)
    275         self.nesBackground.pos = (0,
    276          0,
    277          newWidth,
    278          newHeight)
    279 
    280     def CreateContentLayout(self):
    281         self.contentScroll = OfferScrollContainer(parent=self.contentContainer, align=uiconst.TOALL)
    282         self.grid = OfferGrid(parent=self.contentScroll, align=uiconst.TOTOP, contentSpacing=(CONTENT_PADDING, CONTENT_PADDING), padBottom=CONTENT_PADDING)
    283         self.contentScroll.RegisterContentLoader(self.grid)
    284 
    285     def CreateRedeemingPanelLayout(self):
    286         instructionText = '<url=localsvc:method=ShowRedeemUI>%s</url>' % (GetByLabel('UI/RedeemWindow/ClickToInitiateRedeeming'),)
    287         redeemPanelClass = GetOldRedeemPanel()
    288         self.redeemingPanel = redeemPanelClass(parent=self, align=uiconst.TOBOTTOM, dragEnabled=False, instructionText=instructionText, redeemButtonBackgroundColor=REDEEM_BUTTON_BACKGROUND_COLOR, redeemButtonFillColor=REDEEM_BUTTON_FILL_COLOR)
    289         self.redeemingPanel.UpdateDisplay()
    290 
    291     def _SelectCategory(self, categoryId):
    292         self.selectedRootCategoryId = categoryId
    293         for button in self.categoryButtons:
    294             if button.isActive and button.categoryId != categoryId:
    295                 button.SetActive(False)
    296             elif button.categoryId == categoryId:
    297                 button.SetActive(True)
    298 
    299     def _SelectSubCategory(self, subcategoryId):
    300         self.selectedCategoryId = subcategoryId
    301         for button in self.subcategoryButtons:
    302             if button.isActive and button.categoryId != subcategoryId:
    303                 button.SetActive(False)
    304             elif button.categoryId == subcategoryId:
    305                 button.SetActive(True)
    306 
    307     def OnClickCategory(self, categoryId):
    308         self.LoadCategoryPage(categoryId)
    309 
    310     @RunThreadOnce(THREAD_KEY_LOAD_PAGE)
    311     def LoadCategoryPage(self, categoryId):
    312         if self.page == PAGE_CATEGORY and self.selectedRootCategoryId == categoryId and self.selectedCategoryId is None:
    313             return
    314         logger.debug('Loading category page: %s', categoryId)
    315         self.page = PAGE_CATEGORY
    316         self._SelectCategory(categoryId)
    317         self.ConstructSubCategoryButtons(categoryId)
    318         self._SelectSubCategory(None)
    319         self.SetOffersAndTags(categoryId)
    320         self.category = categoryId
    321         self.subCategory = None
    322         self._NotifyStorePageLoaded()
    323 
    324     def GetSubCategories(self, categoryId):
    325         if categoryId is None:
    326             return
    327         categoriesById = self.store.GetCategories()
    328         category = categoriesById[categoryId]
    329         subcategories = [ categoriesById[subCatId] for subCatId in category.subcategories ]
    330         subcategories = Sort(subcategories, key=lambda c: c.name)
    331         return subcategories
    332 
    333     def OnClickSubCategory(self, subcategoryId):
    334         self.LoadSubCategoryPage(subcategoryId)
    335 
    336     def SelectCategory(self, categoryId):
    337         if categoryId in self.store.GetRootCategoryList():
    338             self.LoadCategoryPage(categoryId)
    339         else:
    340             self.LoadSubCategoryPage(categoryId)
    341 
    342     def SelectCategoryByCategoryTag(self, categoryTag):
    343         categoryId = self.store.GetCategoryIdByCategoryTag(categoryTag)
    344         self.SelectCategory(categoryId)
    345 
    346     @RunThreadOnce(THREAD_KEY_LOAD_PAGE)
    347     def LoadSubCategoryPage(self, subcategoryId):
    348         if self.page == PAGE_SUBCATEGORY and self.selectedCategoryId == subcategoryId:
    349             return
    350         logger.debug('Loading sub category page: %s', subcategoryId)
    351         self.page = PAGE_SUBCATEGORY
    352         self.selectedCategoryId = subcategoryId
    353         self.SetOffersAndTags(subcategoryId)
    354         rootCategoryId = self.store.GetCategories()[subcategoryId].parentId
    355         self._ConstructSubCategoryButtons(rootCategoryId)
    356         self._SelectCategory(rootCategoryId)
    357         self._SelectSubCategory(subcategoryId)
    358         self.subCategory = subcategoryId
    359         self._NotifyStorePageLoaded()
    360 
    361     def SetOffersAndTags(self, categoryId):
    362         tags = self.store.GetTagsByCategoryId(categoryId)
    363         self.SetFilterTags(tags)
    364         tagIds = self.filterBar.GetSelectedFilterTagIds()
    365         offers = self.store.GetFilteredOffers(categoryId, None, tagIds)
    366         self.SetOffers(offers)
    367 
    368     @RunThreadOnce(THREAD_KEY_LOAD_PAGE)
    369     def OnClickHomeButton(self):
    370         self.LoadLandingPage()
    371 
    372     def LoadLandingPage(self):
    373         logger.debug('LoadLandingPage')
    374         if self.page == PAGE_HOME:
    375             return
    376         logger.debug('Loading landing page')
    377         self.page = PAGE_HOME
    378         self._ConstructSubCategoryButtons(None)
    379         self._SelectCategory(None)
    380         self._SelectSubCategory(None)
    381         self.SetFilterTags([])
    382         offers = self.store.GetOffers().values()
    383         self.SetOffers(offers)
    384         self.category = None
    385         self.subCategory = None
    386         self._NotifyStorePageLoaded()
    387 
    388     def __find_button_label(self, button_list, category_id):
    389         for button in button_list:
    390             if button.categoryId == category_id:
    391                 return button.name
    392 
    393     def _NotifyStorePageLoaded(self):
    394         page_name = None
    395         if self.page == 1:
    396             page_name = 'LogoHomeButton'
    397         elif self.page == 2:
    398             page_name = self.__find_button_label(self.categoryButtons, self.category)
    399         elif self.page == 3:
    400             page_name = self.__find_button_label(self.subcategoryButtons, self.subCategory)
    401         sm.ScatterEvent('OnNESPageLoaded', page_name)
    402 
    403     @postponeUntilFocus
    404     def SetPlexBalance(self, balance, *args, **kwargs):
    405         uicore.animations.MorphScalar(self, 'currentPlexBalance', startVal=float(self.currentPlexBalance), endVal=float(balance), curveType=uiconst.ANIM_SMOOTH, duration=1.5, callback=lambda : self.SetCurrentPlexBalance(balance))
    406 
    407     def SetCurrentPlexBalance(self, balance):
    408         self._currentPlexBalance = balance
    409         if not self.destroyed:
    410             self.aurLabel.SetAmount(self._currentPlexBalance)
    411 
    412     def GetCurrentPlexBalance(self):
    413         return self._currentPlexBalance
    414 
    415     currentPlexBalance = property(GetCurrentPlexBalance, SetCurrentPlexBalance)
    416 
    417     def SetCategories(self, categories):
    418         logger.debug('SetCategories %s', categories)
    419         self.categoryContainer.Flush()
    420         searchContainer = Container(name='SearchBox', parent=self.categoryContainer, width=SEARCH_BOX_WIDTH, align=uiconst.TORIGHT)
    421         categoryButtonsContainer = GridContainer(name='ButtonGrid', parent=self.categoryContainer, align=uiconst.TOALL, columns=len(categories), lines=1)
    422         tagById = self.store.GetTags()
    423         self.categoryButtons = []
    424         for category in categories:
    425             button = CategoryButton(parent=categoryButtonsContainer, categoryId=category.id, label=category.name, align=uiconst.TOALL, onClick=self.OnClickCategory, padRight=1, name='CategoryButton_{}'.format(category.id))
    426             self.categoryButtons.append(button)
    427             tags = []
    428             for tagId in category.tagIds:
    429                 tag = tagById.get(tagId)
    430                 if tag:
    431                     tags.append(Tag(tag.id, tag.name))
    432 
    433             self.tagsByCategoryId[category.id] = tags
    434 
    435         iconContainer = Container(name='SearchIconContainer', parent=searchContainer, width=CATEGORIES_HEIGHT, align=uiconst.TOLEFT, bgColor=CATEGORY_COLOR)
    436         Sprite(parent=iconContainer, texturePath='res:/UI/Texture/Vgs/Search_icon.png', width=32, height=32, align=uiconst.CENTER)
    437         self.searchEdit = SingleLineEditText(parent=searchContainer, align=uiconst.TORIGHT, pos=(0,
    438          0,
    439          SEARCH_BOX_WIDTH - CATEGORIES_HEIGHT - 2,
    440          0), fontsize=16, padding=(1, 0, 0, 0), OnChange=self.LoadSearchPage, bgColor=TAG_COLOR)
    441         self.searchEdit.ShowClearButton(icon='res:/UI/Texture/Icons/73_16_45.png')
    442         self.searchEdit.SetHistoryVisibility(False)
    443         self.searchEdit.underlay.Hide()
    444 
    445     @RunThreadOnce(THREAD_KEY_LOAD_PAGE)
    446     def LoadSearchPage(self, searchString):
    447         if len(searchString) > 0:
    448             self.page = PAGE_SEARCH
    449             self._SelectSubCategory(None)
    450             self._SelectCategory(None)
    451             self._ConstructSubCategoryButtons(None)
    452             self.SetFilterTags([])
    453             self.viewStateService.GetView(ViewState.VirtualGoodsStore).Search(searchString)
    454         else:
    455             self.LoadLandingPage()
    456 
    457     @RunThreadOnce(THREAD_KEY_LOAD_PAGE)
    458     def LoadTypeIdPage(self, typeIds):
    459         self.page = PAGE_SEARCH
    460         self._SelectSubCategory(None)
    461         self._SelectCategory(None)
    462         self._ConstructSubCategoryButtons(None)
    463         self.SetFilterTags([])
    464         offers = self.store.SearchOffersByTypeIDs(typeIds)
    465         if len(offers) == 1:
    466             offer = offers[0]
    467             categoryId = self.store.GetSubCategoryId(offer.categories)
    468             self.vgsService.ShowOffer(offer.id, categoryId)
    469         else:
    470             self.SetOffers(offers)
    471 
    472     @RunThreadOnce('VGS.SetSubCategories')
    473     def ConstructSubCategoryButtons(self, categoryId):
    474         self._ConstructSubCategoryButtons(categoryId)
    475 
    476     def _ConstructSubCategoryButtons(self, categoryId):
    477         subcategories = self.GetSubCategories(categoryId)
    478         self.subCategoryButtonContainer.Flush()
    479         self.subcategoryButtons = []
    480         if subcategories is None:
    481             if self.subCategoryContainer.height > 0:
    482                 uicore.animations.MorphScalar(self.subCategoryContainer, attrName='height', startVal=self.subCategoryContainer.height, endVal=0, duration=0.5, callback=self.SetSidebarContentMask)
    483         else:
    484             if int(self.subCategoryContainer.height) != CATEGORIES_HEIGHT:
    485                 uicore.animations.MorphScalar(self.subCategoryContainer, attrName='height', startVal=self.subCategoryContainer.height, endVal=CATEGORIES_HEIGHT, duration=0.5, sleep=False, callback=self.SetSidebarContentMask)
    486             for subcategory in subcategories:
    487                 button = SubCategoryButton(parent=self.subCategoryButtonContainer, label=subcategory.name, align=uiconst.NOALIGN, height=CATEGORIES_HEIGHT, categoryId=subcategory.id, onClick=self.OnClickSubCategory, name='SubCategoryButton_{}'.format(subcategory.id))
    488                 self.subcategoryButtons.append(button)
    489 
    490     def SetOffers(self, offers):
    491         offersToDisplay = []
    492         featuredOffersToDisplay = []
    493         for offer in offers:
    494             if not offer.canPurchase:
    495                 continue
    496             if offer.id in self.featuredOfferIDs:
    497                 featuredOffersToDisplay.append(offer)
    498             else:
    499                 offersToDisplay.append(offer)
    500 
    501         regularOffers = []
    502         specialOffers = {}
    503         for o in offersToDisplay:
    504             if o.label is not None:
    505                 labelName = o.label.name
    506                 if labelName not in Ribbon.SORTING:
    507                     logger.error('Unknown VGS offer label name %s for offer ID %d' % (labelName, o.id))
    508                     continue
    509                 if labelName not in specialOffers:
    510                     specialOffers[labelName] = [o]
    511                 else:
    512                     specialOffers[labelName].append(o)
    513             else:
    514                 regularOffers.append(o)
    515 
    516         orderedOffers = []
    517         if featuredOffersToDisplay:
    518             orderedOffers.extend(SortOffers(featuredOffersToDisplay))
    519         for offerLabel in Ribbon.SORTING:
    520             if offerLabel in specialOffers:
    521                 orderedOffers.extend(SortOffers(specialOffers[offerLabel]))
    522 
    523         orderedOffers.extend(SortOffers(regularOffers))
    524         self.grid.SetOffers(orderedOffers, show_skinr_banner=self.should_show_skinr_banner)
    525 
    526     def SetFilterOptions(self):
    527         self.filterContainer.Flush()
    528         Fill(name='SlipUnderLayer', bgParent=self.filterContainer, color=(0.25, 0.25, 0.25, 1.0), opacity=CONTENT_SLIP_UNDER_AREA_OPACITY, padTop=-HEADER_PADDING * 2)
    529         options = [(GetByLabel('UI/VirtualGoodsStore/Sorting/ByPriceAscending'), SORT_PRICE_ASCENDING),
    530          (GetByLabel('UI/VirtualGoodsStore/Sorting/ByPriceDescending'), SORT_PRICE_DESCENDING),
    531          (GetByLabel('UI/VirtualGoodsStore/Sorting/ByNameAscending'), SORT_NAME_ASCENDING),
    532          (GetByLabel('UI/VirtualGoodsStore/Sorting/ByNameDescending'), SORT_NAME_DESCENDING)]
    533         self.filterCombo = VgsFilterCombo(parent=self.filterContainer, align=uiconst.TORIGHT, options=options, callback=self.OnSortOrderChanged, select=GetSortOrder(), padding=(4, 2, 0, 4))
    534         self.filterBar = VgsOfferFilterBar(parent=self.filterContainer, onFilterChanged=self.OnFilterChanged)
    535 
    536     def SetFilterTags(self, tags):
    537         activeTags = self.activeTagsByRootCategoryId.get(self.GetSelectedRootCategoryId(), {})
    538         self.subCategoryContainer.state = uiconst.UI_PICKCHILDREN
    539         self.filterBar.SetTags(tags, activeTags)
    540 
    541     def OnSortOrderChanged(self, combo, key, value):
    542         settings.user.ui.Set('VgsOfferSortOrder', value)
    543         self.viewStateService.GetView(ViewState.VirtualGoodsStore)._LogFilterChange(value)
    544         self.SetOffers(self.grid.GetOffers())
    545 
    546     def OnFilterChanged(self):
    547         tagIds = self.filterBar.GetSelectedFilterTagIds()
    548         rootCategoryId = self.GetSelectedRootCategoryId()
    549         self.activeTagsByRootCategoryId[rootCategoryId] = tagIds
    550         filterCategory = self.selectedCategoryId or rootCategoryId
    551         filterCurrency = self.selectedCurrency
    552         offers = self.store.GetFilteredOffers(filterCategory, filterCurrency, tagIds)
    553         self.SetOffers(offers)
    554 
    555     def GetSelectedCategoryId(self):
    556         return self.selectedCategoryId
    557 
    558     def GetSelectedRootCategoryId(self):
    559         return self.selectedRootCategoryId
    560 
    561     def OnMouseWheel(self, dz, *args):
    562         self.contentScroll.OnMouseWheel(dz)
    563 
    564     def OnOfferPurchase(self, offerID):
    565         if not self.store.GetOffer(offerID).canPurchase:
    566             self.grid.RemoveOffer(offerID, show_skinr_banner=self.should_show_skinr_banner)
    567 
    568     def SetFeaturedOffers(self, offerIDs):
    569         self.featuredOfferIDs = offerIDs or []
    570         self.SetOffers(self.grid.GetOffers())
    571 
    572     @property
    573     def should_show_skinr_banner(self):
    574         if session.charid is None:
    575             return False
    576         elif self.page == PAGE_HOME:
    577             return True
    578         elif self.selectedRootCategoryId == ROOT_CATEGORY_SHIP_SKINS:
    579             return True
    580         else:
    581             return False