vgsOffer.py (18728B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\view\aurumstore\vgsOffer.py 2 import blue 3 import industry 4 import itertools 5 import logging 6 import math 7 import signals 8 import uthread 9 from carbon.common.script.sys.serviceConst import ROLE_PROGRAMMER 10 from carbonui import const as uiconst 11 from carbonui.fontconst import STYLE_HEADER 12 from carbonui.primitives.container import Container 13 from carbonui.primitives.containerAutoSize import ContainerAutoSize 14 from carbonui.primitives.fill import Fill 15 from carbonui.primitives.frame import Frame 16 from carbonui.primitives.gradientSprite import GradientSprite 17 from carbonui.primitives.sprite import Sprite 18 from carbonui.primitives.transform import Transform 19 from carbonui.uicore import uicore 20 from carbonui.util.color import Color 21 from eve.client.script.ui.control.eveLabel import Label 22 from eve.client.script.ui.control.eveLoadingWheel import LoadingWheel 23 from eve.client.script.ui.shared.preview import PreviewContainer 24 from eve.client.script.ui.shared.vgs.priceTag import PriceTagMedium 25 from eve.client.script.ui.util.uiComponents import Component, ButtonEffect, RadioButtonEffect, ToggleButtonEffect 26 from eve.client.script.ui.view.aurumstore.shared.offerpricing import iter_currencies 27 from eve.client.script.ui.view.aurumstore import vgsUiConst 28 from eve.client.script.ui.view.aurumstore.shared.const import STORE_PREVIEW_OVERLAY_HEIGHT 29 from eve.client.script.ui.view.aurumstore.vgsUiPrimitives import LazyUrlSprite, VgsLabelRibbon, VgsLabelRibbonLarge 30 from eve.common.lib import appConst 31 from eve.common.script.sys.eveCfg import IsApparel, IsBlueprint, IsPreviewable 32 logger = logging.getLogger(__name__) 33 34 class Ribbon(Container): 35 ID_SALE = 'Sale' 36 ID_NEW = 'New' 37 ID_HOT = 'Hot' 38 ID_BUNDLE = 'Bundle' 39 ID_FREE = 'Free' 40 SORTING = [ID_FREE, 41 ID_SALE, 42 ID_NEW, 43 ID_HOT, 44 ID_BUNDLE] 45 default_name = 'Ribbon' 46 default_state = uiconst.UI_DISABLED 47 default_align = uiconst.TOPLEFT 48 49 def __init__(self, text, texture_path, is_big = False, *args, **kwargs): 50 super(Ribbon, self).__init__(*args, **kwargs) 51 self.text = text 52 self.texture_path = texture_path 53 self.is_big = is_big 54 self.construct_layout() 55 56 def construct_layout(self): 57 if self.is_big: 58 self.left = vgsUiConst.OFFER_INFO_PADDING_BIG 59 label = VgsLabelRibbonLarge(parent=self, align=uiconst.CENTER, text=self.text, padding=(-20, 10, 0, 0), color=Color.WHITE) 60 else: 61 label = VgsLabelRibbon(parent=self, align=uiconst.CENTER, text=self.text, padding=(-10, 19, 0, 0), color=Color.WHITE) 62 self.bgSprite = Frame(parent=self, texturePath=self.texture_path, align=uiconst.TOPLEFT, height=50, cornerSize=20, uiScaleVertices=True) 63 self.width = label.textwidth + 70 64 self.height = label.textheight + 2 65 self.bgSprite.width = self.width 66 67 68 def CreateFill(parent, _): 69 return Fill(name='highlight', bgParent=parent.imageLayer, color=vgsUiConst.OFFER_BACKGROUND_COLOR, idx=0) 70 71 72 @Component(ButtonEffect(bgElementFunc=CreateFill, idx=0, opacityIdle=0.0, opacityHover=0.5, opacityMouseDown=0.85, audioOnEntry='store_hover')) 73 74 class VgsOffer(Container): 75 default_name = 'Offer' 76 default_align = uiconst.TOPLEFT 77 default_clipChildren = False 78 default_state = uiconst.UI_NORMAL 79 80 def ApplyAttributes(self, attributes): 81 Container.ApplyAttributes(self, attributes) 82 self.offer = attributes.offer 83 self.name = 'offer_{}'.format(self.offer.id) 84 if self.offer: 85 self.CreateInfoBox(attributes.upperText, attributes.upperSize or vgsUiConst.VGS_FONTSIZE_OFFER) 86 self.imageLayer = Transform(name='imageLayer', parent=self, align=uiconst.TOALL, scalingCenter=(0.5, 0.5), bgColor=vgsUiConst.OFFER_BACKGROUND_COLOR) 87 if self.offer and self.offer.label: 88 Ribbon(parent=self, align=uiconst.TOPLEFT, text=self.offer.label.description, texture_path=self.offer.label.url, state=uiconst.UI_DISABLED, idx=0, padding=(-10, -10, 0, 0)) 89 if self.offer: 90 self.lazySprite = LazyUrlSprite(parent=self.imageLayer, align=uiconst.TOALL, imageUrl=self.offer.imageUrl) 91 GradientSprite(name='OfferGradient', align=uiconst.TOALL, bgParent=self.imageLayer, rgbData=((1.0, vgsUiConst.OFFER_RADIAL_SHADOW),), alphaData=((0.0, 0.0), (1.0, 1.0)), radial=True, idx=0) 92 93 def OnClick(self): 94 if self.offer: 95 sm.GetService('vgsService').ShowOffer(self.offer.id) 96 97 def CreateInfoBox(self, upperText, upperSize): 98 textLayer = Container(name='TextLayer', parent=self, align=uiconst.TOALL) 99 infoBox = ContainerAutoSize(name='InfoBox', parent=textLayer, align=uiconst.TOBOTTOM, height=vgsUiConst.OFFER_INFO_BOX_HEIGHT, state=uiconst.UI_DISABLED, bgColor=vgsUiConst.OFFER_TEXT_BOX_COLOR) 100 Label(parent=infoBox, align=uiconst.TOTOP, text=upperText, fontsize=upperSize, padding=(vgsUiConst.OFFER_INFO_PADDING, 101 2, 102 vgsUiConst.OFFER_INFO_PADDING, 103 -2), fontStyle=STYLE_HEADER, uppercase=True, lineSpacing=-0.15, color=Color.WHITE) 104 priceCont = Container(parent=infoBox, align=uiconst.TOTOP, height=len(self.offer.offerPricings) * 24, padding=(8, 2, 8, 2)) 105 for currency, price, basePrice in iter_currencies(self.offer): 106 PriceTagMedium(parent=priceCont, align=uiconst.TOTOP, currency=currency, price=price, basePrice=basePrice) 107 108 def OnMouseEnter(self, *args): 109 uicore.animations.Tr2DScaleTo(self.imageLayer, startScale=self.imageLayer.scale, endScale=(1.02, 1.02), duration=0.2) 110 111 def OnMouseExit(self, *args): 112 uicore.animations.Tr2DScaleTo(self.imageLayer, self.imageLayer.scale, endScale=(1.0, 1.0), duration=0.2) 113 114 def GetMenu(self, *args): 115 if session.role & ROLE_PROGRAMMER: 116 return [('offerid: %s' % self.offer.id, lambda : blue.pyos.SetClipboardData(str(self.offer.id)))] 117 118 119 class VgsOfferPreview(Container): 120 default_name = 'OfferPreview' 121 default_align = uiconst.TOPLEFT 122 default_state = uiconst.UI_PICKCHILDREN 123 default_clipChildren = True 124 charID = industry.Property('_charID', 'on_charid') 125 typeID = industry.Property('_typeID', 'on_typeid') 126 127 def ApplyAttributes(self, attributes): 128 Container.ApplyAttributes(self, attributes) 129 self.offer = attributes.offer 130 self._charID = None 131 self._typeID = None 132 self.on_charid = signals.Signal(signalName='on_charid') 133 self.on_typeid = signals.Signal(signalName='on_typeid') 134 self.on_charid.connect(self.OnPickCharacter) 135 self.on_typeid.connect(self.OnPickType) 136 self.charButtons = [] 137 self._charButtonsDisplayed = False 138 self.isPreviewOffer = False 139 for product in self.offer.productQuantities.itervalues(): 140 if IsPreviewable(GetPreviewType(product.typeId)): 141 self.typeID = product.typeId 142 self.isPreviewOffer = True 143 144 if not self.isPreviewOffer: 145 self._openServiceOfferCard() 146 elif not IsApparel(self.typeID): 147 self._openSkinOfferCard() 148 else: 149 self._openApperalOfferCard() 150 151 def _createOfferCard(self): 152 textLayer = Container(name='TextLayer', parent=self) 153 descriptionBox = ContainerAutoSize(parent=textLayer, align=uiconst.TOBOTTOM, bgColor=vgsUiConst.OFFER_TEXT_BOX_COLOR, clipChildren=True, name='DescriptionBox') 154 descriptionText = Label(parent=descriptionBox, align=uiconst.TOTOP, text=self.offer.description, fontsize=vgsUiConst.VGS_FONTSIZE_SMALL, padding=vgsUiConst.STORE_OFFER_CARD_DESCRIPTION_PADDING, name='DescriptionText') 155 titleBox = ContainerAutoSize(name='InfoBox', parent=textLayer, align=uiconst.TOBOTTOM, bgColor=vgsUiConst.OFFER_TEXT_BOX_COLOR) 156 titleText = Label(parent=titleBox, align=uiconst.TOTOP, text=self.offer.name, fontsize=vgsUiConst.VGS_FONTSIZE_LARGE, padding=vgsUiConst.STORE_OFFER_CARD_TITLE_PADDING, fontStyle=STYLE_HEADER, uppercase=True, lineSpacing=-0.15, name='TitleText') 157 textLayerScreenHeight = textLayer.GetAbsoluteSize()[1] 158 descriptionScreenHeight = descriptionText.GetAbsoluteSize()[1] 159 titleBoxScreenHeight = titleBox.GetAbsoluteSize()[1] 160 titleTextScreenHeight = titleText.GetAbsoluteSize()[1] 161 descriptionMaxHeight = textLayerScreenHeight - (titleBoxScreenHeight + titleTextScreenHeight) - STORE_PREVIEW_OVERLAY_HEIGHT 162 if descriptionScreenHeight > 50: 163 descriptionBox.maxHeight = 50 164 self._makeCollapsable(parent=textLayer, target=descriptionBox, contentMaxHeight=descriptionMaxHeight, contentMinHeight=50) 165 166 def _addLoading(self): 167 self.loadingLayer = Container(parent=self, align=uiconst.TOALL, state=uiconst.UI_DISABLED) 168 self.loadingWheel = LoadingWheel(parent=self.loadingLayer, align=uiconst.CENTER, state=uiconst.UI_DISABLED, opacity=0.0) 169 self.cover = Sprite(parent=self.loadingLayer, align=uiconst.TOALL, texturePath='res:/UI/Texture/preview/asset_preview_background.png') 170 171 def _addPreviewImageLayer(self): 172 self.imageLayer = Transform(name='imageLayer', parent=self, align=uiconst.TOALL, scalingCenter=(0.5, 0.5), bgColor=vgsUiConst.OFFER_BACKGROUND_COLOR) 173 self.previewContainer = PreviewContainer(parent=self.imageLayer, OnStartLoading=self.OnStartLoading, OnStopLoading=self.OnStopLoading) 174 self.lazySprite = LazyUrlSprite(parent=self.imageLayer, align=uiconst.TOALL, imageUrl=self.offer.imageUrl, state=uiconst.UI_DISABLED) 175 GradientSprite(name='OfferGradient', align=uiconst.TOALL, bgParent=self.imageLayer, rgbData=((1.0, vgsUiConst.OFFER_RADIAL_SHADOW),), alphaData=((0.0, 0.0), (1.0, 1.0)), radial=True, idx=0) 176 177 def _addCharacterPicker(self): 178 self.characterPickerLayer = ContainerAutoSize(parent=self, align=uiconst.TOPLEFT, state=uiconst.UI_PICKCHILDREN, top=50 if self.offer.label else 10, left=10) 179 self.CreateCharacterButtons() 180 181 def _openServiceOfferCard(self): 182 self._createOfferCard() 183 self._addLoading() 184 self.imageLayer = Transform(name='imageLayer', parent=self, align=uiconst.TOALL, scalingCenter=(0.5, 0.5), bgColor=vgsUiConst.OFFER_BACKGROUND_COLOR) 185 self.lazySprite = LazyUrlSprite(parent=self.imageLayer, align=uiconst.TOALL, imageUrl=self.offer.imageUrl, state=uiconst.UI_DISABLED) 186 GradientSprite(name='OfferGradient', align=uiconst.TOALL, bgParent=self.imageLayer, rgbData=((1.0, vgsUiConst.OFFER_RADIAL_SHADOW),), alphaData=((0.0, 0.0), (1.0, 1.0)), radial=True, idx=0) 187 uicore.animations.SpMaskOut(self.cover, duration=0.4) 188 189 def _openSkinOfferCard(self): 190 self._createOfferCard() 191 self._addLoading() 192 self._addPreviewImageLayer() 193 194 def _openApperalOfferCard(self): 195 self._createOfferCard() 196 self._addCharacterPicker() 197 self._addLoading() 198 self._addPreviewImageLayer() 199 200 def _makeCollapsable(self, parent, target, contentMaxHeight, contentMinHeight): 201 collapseBox = ContainerAutoSize(parent=parent, align=uiconst.TOBOTTOM_NOPUSH, top=-36, name='CollapseBox') 202 CollapseButton(parent=collapseBox, align=uiconst.TOPRIGHT, target=target, contentMaxHeight=contentMaxHeight, contentCollapsedHeight=contentMinHeight, name='CollapseButton') 203 204 def CreateCharacterButtons(self): 205 characters = sm.GetService('cc').GetCharactersToSelect() 206 charIDList = itertools.chain([None], map(lambda c: c.characterID, characters)) 207 for i, charID in enumerate(charIDList): 208 button = CharacterButton(parent=self.characterPickerLayer, align=uiconst.RELATIVE, pos=(0, 209 i * 43, 210 38, 211 38), charID=charID, onClick=lambda charID: setattr(self, 'charID', charID), isActive=charID == self.charID, opacity=0.0, state=uiconst.UI_DISABLED) 212 self.on_charid.connect(button.OnCharID) 213 self.on_typeid.connect(button.OnTypeID) 214 self.charButtons.append(button) 215 216 def ShowCharacterButtons(self): 217 if self._charButtonsDisplayed: 218 return 219 for i, button in enumerate(self.charButtons): 220 uicore.animations.MoveInFromLeft(button, amount=10, duration=0.2, timeOffset=i * 0.1) 221 uicore.animations.FadeIn(button, duration=0.4, timeOffset=i * 0.1) 222 button.state = uiconst.UI_NORMAL 223 224 self._charButtonsDisplayed = True 225 226 def HideCharacterButtons(self): 227 if not self._charButtonsDisplayed: 228 return 229 for button in self.charButtons: 230 uicore.animations.FadeOut(button, duration=0.2) 231 button.state = uiconst.UI_DISABLED 232 233 self._charButtonsDisplayed = False 234 235 def OnPickCharacter(self, _): 236 uthread.new(self.ShowPreview) 237 238 def OnPickType(self, _): 239 if IsPreviewable(self.typeID) and IsApparel(self.typeID) and not IsWearableBy(self.typeID, self.charID): 240 self.charID = None 241 return 242 uthread.new(self.ShowPreview) 243 244 def OnStartLoading(self, _): 245 if not IsApparel(self.typeID): 246 self.HideCharacterButtons() 247 else: 248 self.lazySprite.SetOpacity(0) 249 if self.isPreviewOffer: 250 uicore.animations.FadeIn(self.loadingWheel, duration=0.3, timeOffset=0.2) 251 uicore.animations.SpMaskIn(self.cover, duration=0.5, sleep=True) 252 self.previewContainer.Show() 253 254 def OnStopLoading(self, preview, success): 255 if IsApparel(self.typeID): 256 self.ShowCharacterButtons() 257 else: 258 self.lazySprite.SetOpacity(1) 259 if self.isPreviewOffer: 260 uicore.animations.FadeOut(self.loadingWheel, duration=0.3) 261 uicore.animations.SpMaskOut(self.cover, duration=0.5) 262 263 def ShowPreview(self): 264 typeID = GetPreviewType(self.typeID) 265 if not IsPreviewable(typeID): 266 return 267 if IsApparel(self.typeID) and self.charID: 268 self.previewContainer.PreviewCharacter(self.charID, apparel=[self.typeID]) 269 else: 270 self.previewContainer.PreviewType(typeID, scenePath='res:/dx9/scene/fitting/fitting.red') 271 if IsApparel(typeID): 272 self.previewContainer.AnimEntry(-0.1 + math.pi, 0.0, 0.5 + math.pi, -0.2) 273 else: 274 self.previewContainer.AnimEntry(0.3 + math.pi, 0.2, 0.6 + math.pi, -0.3) 275 276 def PrepareClose(self): 277 uicore.animations.SpMaskIn(self.cover, duration=0.2, sleep=True) 278 if self.isPreviewOffer: 279 self.previewContainer.SetState(uiconst.UI_HIDDEN) 280 281 282 @Component(ToggleButtonEffect(bgElementFunc=lambda parent, _: parent.arrow, opacityIdle=0.5, opacityHover=0.8, opacityMouseDown=1.0, audioOnEntry='store_hover')) 283 284 class CollapseButton(Container): 285 default_state = uiconst.UI_NORMAL 286 default_width = 32 287 default_height = 32 288 289 def ApplyAttributes(self, attributes): 290 Container.ApplyAttributes(self, attributes) 291 self.target = attributes.target 292 self.contentMaxHeight = attributes.contentMaxHeight 293 self.contentCollapsedHeight = attributes.contentCollapsedHeight 294 self.arrow = Sprite(parent=self, align=uiconst.TOALL, state=uiconst.UI_DISABLED, texturePath='res:/UI/Texture/Icons/105_32_21.png', rotation=math.pi) 295 296 def Expand(self): 297 self.target.maxHeight = self.contentMaxHeight 298 self.Animate(0, self.target.height, 0) 299 300 def Collapse(self): 301 self.target.maxHeight = self.contentCollapsedHeight 302 self.Animate(self.target.height, 0, math.pi) 303 304 def Animate(self, startHeight, endHeight, rotation): 305 uicore.animations.MorphScalar(self.target, 'height', startVal=startHeight, endVal=endHeight, duration=0.4, curveType=uiconst.ANIM_OVERSHOT) 306 uicore.animations.MorphScalar(self.arrow, 'rotation', startVal=self.arrow.rotation, endVal=rotation, duration=0.2) 307 308 def OnClick(self, *args): 309 if self.isActive: 310 self.Collapse() 311 else: 312 self.Expand() 313 314 315 @Component(RadioButtonEffect(bgElementFunc=lambda parent, _: parent.highlight, idx=0, opacityIdle=0.0, opacityHover=0.5, opacityMouseDown=0.85, audioOnEntry='store_hover')) 316 317 class CharacterButton(Container): 318 default_state = uiconst.UI_NORMAL 319 320 def ApplyAttributes(self, attributes): 321 Container.ApplyAttributes(self, attributes) 322 self.charID = attributes.get('charID', None) 323 self.onClick = attributes.onClick 324 self.gender = None 325 if self.charID: 326 self.gender = cfg.eveowners.Get(self.charID).gender 327 self.highlight = Fill(bgParent=self, color=(0.8, 0.8, 0.8, 1.0), padding=[-3, 328 -3, 329 -2, 330 -2], fillCenter=True) 331 self.portrait = Sprite(parent=self, name='portraitSprite', align=uiconst.TOALL, state=uiconst.UI_DISABLED) 332 if self.charID: 333 sm.GetService('photo').GetPortrait(self.charID, 38, self.portrait) 334 else: 335 self.portrait.texturePath = 'res:/UI/Texture/Vgs/mannequin.png' 336 337 def OnClick(self, *args): 338 if not self.disabled: 339 self.onClick(self.charID) 340 341 def OnCharID(self, container): 342 self.SetActive(container.charID == self.charID) 343 344 def OnTypeID(self, container): 345 if self.gender is None or not IsApparel(container.typeID): 346 return 347 gender = GetApparelGender(container.typeID) 348 if gender is None or gender == self.gender: 349 self.disabled = False 350 uicore.animations.SpColorMorphTo(self.portrait, endColor=(1.0, 1.0, 1.0), duration=0.4) 351 else: 352 self.disabled = True 353 uicore.animations.SpColorMorphTo(self.portrait, endColor=(0.4, 0.4, 0.4), duration=0.4) 354 355 356 GENDER_BY_APPAREL_GENDER = {1: appConst.MALE, 357 2: None, 358 3: appConst.FEMALE, 359 4: 'Missing Gender'} 360 361 def GetApparelGender(typeID): 362 apparelGender = None 363 for resource in cfg.paperdollResources: 364 if resource.typeID == typeID: 365 if resource.resGender is None: 366 apparelGender = None 367 elif resource.resGender: 368 apparelGender = appConst.MALE 369 elif not resource.resGender: 370 apparelGender = appConst.FEMALE 371 372 return apparelGender 373 374 375 def IsWearableBy(typeID, charID): 376 if not IsApparel(typeID): 377 return False 378 if charID is None: 379 return True 380 apparelGender = GetApparelGender(typeID) 381 characterGender = cfg.eveowners.Get(charID).gender 382 return apparelGender is None or apparelGender == characterGender 383 384 385 def GetPreviewType(typeID): 386 if IsBlueprint(typeID): 387 blueprint = sm.GetService('blueprintSvc').GetBlueprintType(typeID) 388 return blueprint.productTypeID 389 return typeID