skinPanel.py (36646B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\skins\skinPanel.py 2 from __future__ import absolute_import 3 import blue 4 import carbonui.text.styles 5 import contextlib 6 import eveformat 7 import evetypes 8 import gametime 9 import locks 10 import log 11 import logging 12 import math 13 import numbers 14 import sys 15 import uthread 16 import uthread2 17 from carbon.common.script.sys.serviceConst import ROLE_GML 18 import carbonui 19 from carbonui import ButtonStyle, ButtonVariant, const as uiconst, Density 20 from carbonui.control.button import Button 21 from carbonui.control.buttonIconHoverSprite import ButtonIconHoverSprite 22 from carbonui.control.scrollContainer import ScrollContainer 23 from carbonui.primitives.container import Container 24 from carbonui.primitives.containerAutoSize import ContainerAutoSize 25 from carbonui.primitives.frame import Frame 26 from carbonui.primitives.sprite import Sprite 27 from carbonui.primitives.line import Line 28 from carbonui.uianimations import animations 29 from .defaultSkinSettings import get_default_skin_id_for_hull, set_default_skin_for_hull, clear_default_skin_for_hull 30 from eve.client.script.ui import eveColor 31 from eve.client.script.ui.control.infoIcon import MoreInfoIcon 32 from eve.client.script.ui.control.themeColored import FillThemeColored, GradientThemeColored 33 from eve.client.script.ui.plex.textures import PLEX_12_SOLID_WHITE 34 from eve.client.script.ui.shared.skins.controller import SkinNotAvailableForType 35 from eve.client.script.ui.shared.skins.devMenuFunctions import GiveSkin, GivePermanentSkin, GiveLimitedSkin, RemoveSkin, GetSkinUsedWithTypeAndMaterial, GiveExpiredSkin 36 from eve.client.script.ui.shared.skins.uiutil import OPEN_SKINR_BUTTON_ANALYTIC_ID 37 from eve.client.script.ui.shared.skins.event import LogBuySkinIsk, LogBuySkinAur 38 from eve.client.script.ui.util.uiComponents import Component, ButtonEffect 39 from cosmetics.common.ships.skins.static_data.skin_type import ShipSkinType 40 from inventorycommon.const import typeSkinMaterial 41 from cosmetics.common.ships.skins.static_data.slot_configuration import is_skinnable_ship 42 from localization import GetByLabel 43 logger = logging.getLogger(__name__) 44 ENTRY_DEFAULT_HEIGHT = 106 45 ENTRY_COMPACT_HEIGHT = 80 46 COMPACT_THRESHOLD = 350 47 48 class SkinPanel(ScrollContainer): 49 default_settingsPrefix = 'SkinPanel' 50 51 def ApplyAttributes(self, attributes): 52 super(SkinPanel, self).ApplyAttributes(attributes) 53 self.controller = attributes.controller 54 self.settingsPrefix = attributes.settingsPrefix or self.default_settingsPrefix 55 self._logContext = attributes.get('logContext', None) 56 self.whatchlist = [] 57 self.loaded = False 58 self._loadingLock = locks.Lock() 59 self.controller.onSkinsChange.connect(self.OnSkinsChange) 60 self.controller.on_position_changed.connect(self.OnPositionChanged) 61 self._licensedSkinGroup = None 62 self._unlicensedSkinGroup = None 63 self._previewType = None 64 self._forcedCompact = attributes.get('compact', False) 65 self._compactMode = False 66 self._paragonHubSection = None 67 sm.RegisterForNotifyEvent(self, 'OnDefaultSkinSet') 68 69 def Reload(self): 70 with self._loadingLock: 71 self.loaded = False 72 self.Flush() 73 self.Load() 74 75 def Load(self, selectedSkin = None): 76 if self.loaded: 77 return 78 self._compactMode = self._forcedCompact or self.GetAbsoluteSize()[0] < COMPACT_THRESHOLD 79 with self._loadingLock: 80 self.Flush() 81 now = gametime.GetWallclockTime() 82 default_skin_id = get_default_skin_id_for_hull(self.controller.typeID) 83 skins = sorted(self.controller.skins, key=lambda skin: skin.name.lower() and skin.skinID != default_skin_id) 84 self._paragonHubSection = OpenParagonHubSection(parent=self, align=uiconst.TOTOP, padBottom=8, display=False) 85 licensedLabel = GetByLabel('UI/ShipCosmetics/LicensedSkins', shipTypeName='') 86 settingName = '%s_LicensedSkins' % self.settingsPrefix 87 licensed = [ skin for skin in skins if skin.licensed and (skin.expires is None or skin.expires > now) ] 88 self._licensedSkinGroup = self.AddSkinGroup(licensedLabel, licensed, settingName=settingName, selectedSkin=selectedSkin) 89 unlicensed = [ skin for skin in skins if not skin.licensed or skin.expires and skin.expires < now ] 90 unlicensedLabel = GetByLabel('UI/ShipCosmetics/UnlicensedSkins', shipTypeName='') 91 settingName = '%s_UnlicensedSkins' % self.settingsPrefix 92 self._unlicensedSkinGroup = self.AddSkinGroup(unlicensedLabel, unlicensed, settingName=settingName, selectedSkin=selectedSkin) 93 for i, entry in enumerate(self.mainCont.children): 94 if hasattr(entry, 'AnimShow') and entry.display: 95 entry.AnimShow(delay=i * 0.05) 96 97 self.loaded = True 98 self.SetPreviewType(self._previewType) 99 100 def AddSkinGroup(self, label, skins, settingName = None, selectedSkin = None): 101 group = SkinGroupEntry(parent=self, text=label, collapsedSettingName=settingName) 102 if len(skins) == 0: 103 message = carbonui.text.styles.TextHeader(parent=self, align=uiconst.TOTOP, padding=(25, 12, 0, 20), text=GetByLabel('UI/Skins/NoSkins'), color=carbonui.TextColor.SECONDARY) 104 group.AddEntry(message) 105 for skin in skins: 106 entry = SkinEntrySlot(parent=self, align=uiconst.TOTOP, padTop=8, controller=self.controller, skin=skin, logContext=self._logContext, selectedSkin=selectedSkin) 107 if self._compactMode: 108 entry.SetCompact() 109 group.AddEntry(entry) 110 111 return group 112 113 def SetPreviewType(self, typeID): 114 self._previewType = typeID 115 if self.loaded and self._previewType: 116 self._SetPreviewText() 117 self._paragonHubSection.display = is_skinnable_ship(typeID) 118 self._paragonHubSection.shipTypeID = typeID 119 120 def SetCompactMode(self, compact): 121 self._forcedCompact = compact 122 if self._forcedCompact: 123 self._SetCompact() 124 return 125 width = self.GetAbsoluteSize()[0] 126 compactMode = width < COMPACT_THRESHOLD 127 if self._compactMode != compactMode: 128 if width < COMPACT_THRESHOLD: 129 self._SetCompact() 130 else: 131 self._SetExpanded() 132 133 def _SetPreviewText(self): 134 shipTypeName = evetypes.GetName(self._previewType) 135 licensedLabel = GetByLabel('UI/ShipCosmetics/LicensedSkins', shipTypeName=shipTypeName) 136 unlicensedLabel = GetByLabel('UI/ShipCosmetics/UnlicensedSkins', shipTypeName=shipTypeName) 137 self._licensedSkinGroup.SetLabelText(licensedLabel) 138 self._unlicensedSkinGroup.SetLabelText(unlicensedLabel) 139 140 def OnSkinsChange(self): 141 self.Reload() 142 143 def OnPositionChanged(self): 144 skinPreviewed = self.controller.previewed 145 groupEntry, skinEntrySlot = self._FindGroupAndSkinElements(skinPreviewed) 146 if groupEntry and hasattr(groupEntry, 'collapsed') and groupEntry.collapsed: 147 groupEntry.ToggleCollapse() 148 if skinEntrySlot: 149 clipperTop = self.clipCont.GetAbsoluteTop() 150 _, clipperHeight = self.clipCont.GetAbsoluteSize() 151 skinEntryTop = skinEntrySlot.GetAbsoluteTop() 152 if skinEntryTop > clipperTop + clipperHeight: 153 rel = skinEntryTop - clipperTop 154 totalHeight = self.mainCont.height 155 pos = min(1.0, max(0, rel / float(totalHeight))) 156 self.ScrollToVertical(pos) 157 158 def _FindGroupAndSkinElements(self, skinPreviewed): 159 for eachChild in self.mainCont.children: 160 if not isinstance(eachChild, SkinGroupEntry): 161 continue 162 for eachEntry in eachChild._entries: 163 if not isinstance(eachEntry, SkinEntrySlot): 164 continue 165 if eachEntry.entry.skin == skinPreviewed: 166 return (eachChild, eachEntry) 167 168 return (None, None) 169 170 def Close(self): 171 super(SkinPanel, self).Close() 172 self.controller.Close() 173 sm.UnregisterForNotifyEvent(self, 'OnDefaultSkinSet') 174 175 def _OnSizeChange_NoBlock(self, width, height): 176 super(SkinPanel, self)._OnSizeChange_NoBlock(width, height) 177 if self._forcedCompact: 178 return 179 if width < COMPACT_THRESHOLD: 180 self._SetCompact() 181 else: 182 self._SetExpanded() 183 184 def _SetCompact(self): 185 if self._compactMode: 186 return 187 self._compactMode = True 188 for eachChild in self.mainCont.children: 189 if not isinstance(eachChild, SkinGroupEntry): 190 continue 191 for eachEntry in eachChild._entries: 192 if not isinstance(eachEntry, SkinEntrySlot): 193 continue 194 eachEntry.SetCompact() 195 196 def _SetExpanded(self): 197 if not self._compactMode: 198 return 199 self._compactMode = False 200 for eachChild in self.mainCont.children: 201 if not isinstance(eachChild, SkinGroupEntry): 202 continue 203 for eachEntry in eachChild._entries: 204 if not isinstance(eachEntry, SkinEntrySlot): 205 continue 206 eachEntry.SetExpanded() 207 208 def OnDefaultSkinSet(self, skin_id): 209 self.Reload() 210 211 212 class EntryMixin(object): 213 214 def AnimShow(self, delay = 0.0): 215 animations.FadeTo(self, duration=0.2, timeOffset=delay) 216 animations.MoveInFromTop(self, curveType=uiconst.ANIM_OVERSHOT, duration=0.3, timeOffset=delay) 217 218 219 class SkinEntrySlot(Container, EntryMixin): 220 default_name = 'SkinEntrySlot' 221 default_alignMode = uiconst.TOTOP 222 default_height = ENTRY_DEFAULT_HEIGHT 223 224 def ApplyAttributes(self, attributes): 225 super(SkinEntrySlot, self).ApplyAttributes(attributes) 226 selectedSkin = attributes.get('selectedSkin', None) 227 controller = attributes.controller 228 skin = attributes.skin 229 logContext = attributes.get('logContext', None) 230 if skin.skin_type == ShipSkinType.THIRD_PARTY_SKIN: 231 self.entry = ThirdPartySkinEntry(parent=self, controller=controller, skin=skin, logContext=logContext, name='SkinEntry_{}'.format(skin.name.encode('utf-8'))) 232 elif skin.skin_type == ShipSkinType.FIRST_PARTY_SKIN: 233 self.entry = SkinEntry(parent=self, controller=controller, skin=skin, logContext=logContext, name='SkinEntry_{}'.format(skin.materialID)) 234 if selectedSkin and selectedSkin.skin_type == ShipSkinType.FIRST_PARTY_SKIN: 235 if selectedSkin.materialID == skin.materialID: 236 self.entry.SetSkinState(BaseSkinEntry.STATE_PREVIEWED) 237 if skin.licensed: 238 skin_id = get_default_skin_id_for_hull(self.entry.controller.typeID) 239 if skin_id is not None and skin_id == self.entry.skin.skinID: 240 self.entry.removeDefaultButtonIcon.opacity = 1.0 241 self.entry.defaultButtonIcon.pickState = uiconst.TR2_SPS_OFF 242 self.entry.removeDefaultButtonIcon.pickState = uiconst.TR2_SPS_ON 243 else: 244 self.entry.defaultButtonIcon.pickState = uiconst.TR2_SPS_OFF 245 self.entry.removeDefaultButtonIcon.pickState = uiconst.TR2_SPS_OFF 246 247 def GetAbsoluteScrollPosition(self): 248 _, top, _, height = self.GetAbsolute() 249 return (top, top + height) 250 251 def AnimShow(self, delay = 0.0): 252 super(SkinEntrySlot, self).AnimShow(delay=delay) 253 if self.entry.parent == self: 254 self.entry.AnimShow(delay=delay) 255 256 def SetCompact(self): 257 self.entry.SetCompact() 258 self.height = ENTRY_COMPACT_HEIGHT 259 260 def SetExpanded(self): 261 self.entry.SetExpanded() 262 self.height = ENTRY_DEFAULT_HEIGHT 263 264 265 @Component(ButtonEffect(opacityIdle=0.0, opacityHover=0.2, opacityMouseDown=0.3, bgElementFunc=lambda parent, _: parent.blinkFill, audioOnEntry=uiconst.SOUND_ENTRY_HOVER, audioOnClick='fitting_window_skin_select_play')) 266 267 class BaseSkinEntry(Container): 268 default_align = uiconst.TOALL 269 default_bgColor = (0.0, 0.0, 0.0, 0.25) 270 default_state = uiconst.UI_NORMAL 271 STATE_IDLE = 1 272 STATE_APPLIED = 2 273 STATE_PENDING = 3 274 STATE_PREVIEWED = 4 275 276 def ApplyAttributes(self, attributes): 277 super(BaseSkinEntry, self).ApplyAttributes(attributes) 278 self.cosmeticsSvc = sm.GetService('cosmeticsSvc') 279 self.controller = attributes.controller 280 self.skin = attributes.skin 281 self.skinstate = self.STATE_IDLE 282 self._logContext = attributes.get('logContext', None) 283 self.warningSprite = None 284 self.Layout() 285 self.UpdateSkinState(0) 286 self.controller.onChange.connect(self.UpdateSkinState) 287 sm.RegisterForNotifyEvent(self, 'OnDefaultSkinSet') 288 289 def Close(self): 290 super(BaseSkinEntry, self).Close() 291 sm.UnregisterForNotifyEvent(self, 'OnDefaultSkinSet') 292 293 @property 294 def iconTexturePath(self): 295 return self.skin.iconTexturePath 296 297 def Layout(self): 298 self._ConstructSelectionCont() 299 self._ConstructIcon() 300 self._ConstructMain() 301 self._ConstructState() 302 self._ConstructFrame() 303 304 def _ConstructSelectionCont(self): 305 self.selectionFrameCont = Container(parent=self, align=uiconst.TOALL) 306 307 def _ConstructIcon(self): 308 self.iconContainer = ContainerAutoSize(name='iconContainer', parent=self, align=uiconst.TOLEFT, state=uiconst.UI_DISABLED, padding=(32, 0, 32, 0)) 309 self.iconInnerContainer = Container(name='iconInnerContainer', parent=self.iconContainer, align=uiconst.CENTER, width=45, height=45) 310 self.iconGlow = Sprite(name='iconGlow', parent=self.iconInnerContainer, align=uiconst.TOALL, texturePath='res:/UI/Texture/classes/skins/skin-icon-glow.png', opacity=0.0) 311 Sprite(name='iconTexture', parent=self.iconInnerContainer, align=uiconst.TOALL, texturePath=self.iconTexturePath) 312 self.iconShadow = Sprite(name='iconShadow', parent=self.iconInnerContainer, align=uiconst.TOALL, texturePath='res:/UI/Texture/classes/skins/icon-shadow.png') 313 self.iconBG = Sprite(name='iconBackground', parent=self.iconContainer, align=uiconst.CENTER, texturePath='res:/UI/Texture/classes/skins/skin_background.png', color=eveColor.SAND_YELLOW, opacity=0.05, width=80, height=80) 314 315 def _ConstructMain(self): 316 self.textCont = VerticalCenteredContainer(name='textCont', parent=self, align=uiconst.TOALL, left=0, padRight=10) 317 carbonui.text.styles.TextBody(name='title', parent=self.textCont, align=uiconst.TOTOP, text=self.skin.name, color=carbonui.TextColor.HIGHLIGHT, maxLines=2) 318 319 def _ConstructState(self): 320 self.pendingIcon = Sprite(name='pendingIcon', parent=self, align=uiconst.TOPRIGHT, state=uiconst.UI_DISABLED, pos=(9, 9, 16, 16), texturePath='res:/UI/Texture/classes/skins/pending.png', opacity=0.0) 321 animations.MorphScalar(self.pendingIcon, 'rotation', startVal=0.0, endVal=-2 * math.pi, duration=1.0, curveType=uiconst.ANIM_LINEAR, loops=uiconst.ANIM_REPEAT) 322 self.appliedIcon = Sprite(name='appliedIcon', parent=self, align=uiconst.TOPRIGHT, state=uiconst.UI_DISABLED, pos=(0, 0, 34, 34), texturePath='res:/UI/Texture/classes/SkillPlan/completedIcon.png', opacity=0.0) 323 self.defaultButtonIcon = ButtonIconHoverSprite(name='defaultIcon', parent=self, align=uiconst.TOPRIGHT, pos=(9, 9, 16, 16), texturePath='res:/UI/Texture/eveicon/system_icons/star_hollow_16px.png', hoverTexture='res:/UI/Texture/eveicon/system_icons/star_16px.png', func=self.SetDefaultSkin, hint=GetByLabel('UI/Skins/SetDefault')) 324 self.defaultButtonIcon.opacity = 0.0 325 self.removeDefaultButtonIcon = ButtonIconHoverSprite(name='removeDefaultIcon', parent=self, align=uiconst.TOPRIGHT, pos=(9, 9, 16, 16), texturePath='res:/UI/Texture/eveicon/system_icons/star_16px.png', hoverTexture='res:/UI/Texture/eveicon/system_icons/star_slash_16px.png', func=self.RemoveDefaultSkin, hint=GetByLabel('UI/Skins/RemoveDefault')) 326 self.removeDefaultButtonIcon.opacity = 0.0 327 self.previewedIcon = Sprite(name='previewedIcon', parent=self, align=uiconst.TOPRIGHT, state=uiconst.UI_DISABLED, pos=(9, 9, 18, 16), texturePath='res:/UI/Texture/classes/skins/previewed.png', opacity=0.0) 328 self.stateIconLight = GradientThemeColored(name='stateIconLight', parent=self, align=uiconst.TOALL, state=uiconst.UI_DISABLED, rotation=0, alphaData=[(0.0, 0.0), 329 (0.25, 0.01), 330 (0.5, 0.1), 331 (0.6, 0.2), 332 (0.7, 0.4), 333 (0.8, 0.8), 334 (0.9, 1.0)], colorType=uiconst.COLORTYPE_UIHILIGHTGLOW, opacity=0.0) 335 336 def _ConstructFrame(self): 337 self.selectionFrame = Frame(name='selectionFrame', parent=self.selectionFrameCont, align=uiconst.TOALL, state=uiconst.UI_DISABLED, texturePath='res:/UI/Texture/classes/Fitting/Heraldry/selectionFrame.png', color=eveColor.SUCCESS_GREEN, cornerSize=10, uiScaleVertices=True, fillCenter=False, padding=(-8, -8, -8, -8), opacity=0.0) 338 self.selectionGradient = FillThemeColored(name='selectionGradient', bgParent=self, align=uiconst.TOALL, state=uiconst.UI_DISABLED, colorType=uiconst.COLORTYPE_UIHILIGHT, opacity=0.0) 339 self.blinkFill = FillThemeColored(name='blinkFill', bgParent=self, colorType=uiconst.COLORTYPE_UIHILIGHT, opacity=0.0) 340 341 def UpdateSkinState(self, delay = None): 342 if self.controller.applied == self.skin: 343 delay = delay if delay is not None else self.controller.GetTimeUntilNextAllowedSkinChange() 344 skinState = self.STATE_APPLIED 345 elif self.controller.pending == self.skin: 346 skinState = self.STATE_PENDING 347 elif self.controller.previewed == self.skin: 348 skinState = self.STATE_PREVIEWED 349 else: 350 skinState = self.STATE_IDLE 351 self.SetSkinState(skinState, delay) 352 353 def SetSkinState(self, state, delay = None): 354 if state == self.skinstate: 355 return 356 if state == self.STATE_IDLE: 357 self.AnimCollapse() 358 elif self.skinstate == self.STATE_IDLE: 359 self.AnimExpand() 360 if state == self.STATE_APPLIED: 361 if self.warningSprite: 362 self.warningSprite.display = False 363 self.AnimStateApplied(delay) 364 elif state == self.STATE_PENDING: 365 self.AnimStatePending() 366 elif state == self.STATE_PREVIEWED: 367 self.AnimStatePreviewed() 368 elif state == self.STATE_IDLE: 369 self.AnimStateIdle() 370 self.UpdateDefaultIconAlign(is_idle=state == self.STATE_IDLE) 371 self.skinstate = state 372 373 def AnimShow(self, delay = 0.0): 374 animations.FadeIn(self.blinkFill, endVal=0.4, curveType=uiconst.ANIM_WAVE, duration=0.4, timeOffset=delay * 1.4 + 0.4) 375 376 def AnimExpand(self): 377 animations.FadeIn(self.selectionFrame, endVal=1.0, duration=0.1, curveType=uiconst.ANIM_OVERSHOT) 378 animations.FadeIn(self.selectionGradient, endVal=0.15, duration=0.1, curveType=uiconst.ANIM_OVERSHOT) 379 self._AnimShowIconGlow() 380 381 def AnimCollapse(self): 382 animations.FadeOut(self.selectionFrame, duration=0.1) 383 animations.FadeOut(self.selectionGradient, duration=0.1) 384 self._AnimHideIconGlow() 385 self._FadeOutSmallIcons() 386 387 def _FadeOutSmallIcons(self): 388 for eachIcon in (self.pendingIcon, self.previewedIcon, self.appliedIcon): 389 animations.FadeOut(eachIcon, duration=0.05) 390 391 def _AnimShowIconGlow(self): 392 self.iconGlow.opacity = 0.05 393 animations.SpMaskIn(self.iconGlow, duration=0.4) 394 395 def _AnimHideIconGlow(self): 396 animations.FadeOut(self.iconGlow, duration=0.3) 397 398 def AnimStateIdle(self): 399 for icon in (self.pendingIcon, self.previewedIcon, self.pendingIcon): 400 animations.StopAllAnimations(icon) 401 animations.FadeOut(icon, duration=0.15) 402 403 def AnimStateApplied(self, delay = None): 404 if delay <= 0: 405 self._AnimStateApplied() 406 else: 407 uthread2.call_after_wallclocktime_delay(self._AnimStateApplied, delay) 408 409 def _AnimStateApplied(self): 410 animations.FadeOut(self.pendingIcon, duration=0.15) 411 animations.FadeOut(self.previewedIcon, duration=0.15) 412 self._AnimShowStateIcon(self.appliedIcon) 413 414 def AnimStatePending(self): 415 animations.FadeOut(self.appliedIcon, duration=0.15) 416 animations.FadeOut(self.previewedIcon, duration=0.15) 417 animations.FadeIn(self.pendingIcon, duration=0.15) 418 419 def AnimStatePreviewed(self): 420 animations.FadeOut(self.appliedIcon, duration=0.15) 421 animations.FadeOut(self.pendingIcon, duration=0.15) 422 self._AnimShowStateIcon(self.previewedIcon) 423 424 def UpdateDefaultIconAlign(self, is_idle): 425 left = 9 if is_idle else 33 426 for icon in (self.defaultButtonIcon, self.removeDefaultButtonIcon): 427 icon.left = left 428 429 def _AnimShowStateIcon(self, icon): 430 431 def continue_glowExpand(): 432 animations.MorphScalar(icon, 'glowExpand', startVal=0.0, endVal=20.0, duration=0.5, curveType=uiconst.ANIM_SMOOTH) 433 animations.SpColorMorphTo(icon, attrName='glowColor', startColor=(1.0, 1.0, 1.0, 0.5), endColor=(0.0, 0.0, 0.0, 0.0), duration=0.25, curveType=uiconst.ANIM_SMOOTH) 434 435 animations.FadeIn(icon, duration=0.15, timeOffset=0.15) 436 animations.SpColorMorphTo(icon, attrName='glowColor', startColor=(0.3, 0.3, 0.3, 0.0), endColor=(1.0, 1.0, 1.0, 0.5), duration=0.15, timeOffset=0.2, curveType=uiconst.ANIM_SMOOTH) 437 animations.MorphScalar(icon, 'glowExpand', startVal=30.0, endVal=0.0, duration=0.2, timeOffset=0.15, curveType=uiconst.ANIM_SMOOTH, callback=continue_glowExpand) 438 animations.FadeTo(self.stateIconLight, startVal=0.0, endVal=0.15, duration=0.55, timeOffset=0.15, curveType=uiconst.ANIM_BOUNCE) 439 440 def Pick(self): 441 try: 442 self.controller.PickSkin(self.skin) 443 except SkinNotAvailableForType: 444 logger.warning('PickSkin failed with SkinNotAvailableForType. Most likely due to the shipID changing in the controller before the UI is torn down.') 445 sys.exc_clear() 446 447 def OnClick(self, *args): 448 self.Pick() 449 450 def SetCompact(self): 451 self.iconContainer.padding = (12, 0, 8, 0) 452 self.iconInnerContainer.SetSize(24, 24) 453 self.iconBG.SetSize(40, 40) 454 self.textCont.padRight = 30 455 456 def SetExpanded(self): 457 self.iconContainer.padding = (32, 0, 32, 0) 458 self.iconInnerContainer.SetSize(46, 46) 459 self.iconBG.SetSize(80, 80) 460 self.textCont.padRight = 10 461 462 def SetDefaultSkin(self): 463 self.defaultButtonIcon.pickState = uiconst.TR2_SPS_OFF 464 self.removeDefaultButtonIcon.pickState = uiconst.TR2_SPS_ON 465 self.defaultButtonIcon.opacity = 0.0 466 self.removeDefaultButtonIcon.opacity = 1.0 467 set_default_skin_for_hull(hull_id=self.controller.typeID, skin_id=self.skin.skinID) 468 469 def RemoveDefaultSkin(self): 470 self.removeDefaultButtonIcon.pickState = uiconst.TR2_SPS_OFF 471 self.defaultButtonIcon.pickState = uiconst.TR2_SPS_ON 472 self.removeDefaultButtonIcon.opacity = 0.0 473 self.defaultButtonIcon.opacity = 1.0 474 clear_default_skin_for_hull(hull_id=self.controller.typeID) 475 476 def OnMouseEnter(self, *args): 477 if self.skin.licensed: 478 skin_id = get_default_skin_id_for_hull(self.controller.typeID) 479 if skin_id is not None and skin_id == self.skin.skinID: 480 animations.FadeIn(self.removeDefaultButtonIcon, duration=0.15) 481 else: 482 animations.FadeIn(self.defaultButtonIcon, duration=0.15) 483 484 def OnMouseExit(self, *args): 485 skin_id = get_default_skin_id_for_hull(self.controller.typeID) 486 if skin_id != self.skin.skinID: 487 animations.FadeOut(self.removeDefaultButtonIcon, duration=0.15) 488 animations.FadeOut(self.defaultButtonIcon, duration=0.15) 489 490 def OnDefaultSkinSet(self, skin_id): 491 if skin_id != self.skin.skinID: 492 animations.FadeOut(self.removeDefaultButtonIcon, duration=0.15) 493 self.removeDefaultButtonIcon.pickState = uiconst.TR2_SPS_OFF 494 self.defaultButtonIcon.pickState = uiconst.TR2_SPS_ON 495 496 497 class SkinEntry(BaseSkinEntry): 498 499 def _ConstructMain(self): 500 super(SkinEntry, self)._ConstructMain() 501 self.warningSprite = Sprite(parent=self, align=uiconst.TOPRIGHT, state=uiconst.UI_NORMAL, pos=(2, 2, 16, 16), texturePath='res:/ui/texture/icons/44_32_7.png', hint=GetByLabel('UI/Skins/WarningNoPermissionToChangeSkin')) 502 self.warningSprite.display = False 503 if self.skin.licensed and not self.skin.expired: 504 SkinDurationLabel(name='durationText', parent=self.textCont, align=uiconst.TOTOP, skin=self.skin, padTop=3) 505 if self.controller.IsSkinAccessRestricted(): 506 self.warningSprite.hint = GetByLabel('UI/Skins/WarningNoPermissionToChangeSkin') 507 self.warningSprite.display = True 508 elif self.controller.IsDisabledDueToDamage(): 509 self.warningSprite.display = True 510 self.warningSprite.hint = GetByLabel('UI/Skins/WarningTooDamagedToChangeSkin') 511 else: 512 buttonCont = Container(name='buttonCont', parent=self.textCont, align=uiconst.TOTOP, height=24, padTop=8) 513 is_single_use = self._AreAllLicensesSingleUse() 514 SkinBuyButtonPlex(parent=buttonCont, align=uiconst.TOLEFT, typeID=self.controller.typeID, materialID=self.skin.materialID, logContext=self._logContext, padRight=4, display=False) 515 SkinBuyButtonIsk(parent=buttonCont, align=uiconst.TOLEFT, typeID=self.controller.typeID, materialID=self.skin.materialID, logContext=self._logContext, state=uiconst.UI_HIDDEN if is_single_use else uiconst.UI_NORMAL, padRight=4) 516 if is_single_use: 517 MoreInfoIcon(name='SingleUseSkinsInfoIcon', parent=buttonCont, align=uiconst.TOLEFT, hint=GetByLabel('UI/Skins/SingleUseHint'), padRight=4) 518 519 def _AreAllLicensesSingleUse(self): 520 return self.cosmeticsSvc.static.AreAllLicensesSingleUse(self.controller.typeID, self.skin.materialID) 521 522 def GetMenu(self): 523 entries = [] 524 gmEntries = self._GetGMMenuEntries() 525 if len(gmEntries) > 0: 526 entries.extend(gmEntries) 527 entries.append(None) 528 entries.extend([[GetByLabel('UI/Commands/ShowInfo'), self.ShowMaterialInfo], None, [GetByLabel('UI/Inventory/ItemActions/ViewTypesMarketDetails'), self.OpenMarketWindow]]) 529 return entries 530 531 def _GetGMMenuEntries(self): 532 if session.role & ROLE_GML != ROLE_GML: 533 return [] 534 elif self.skin.skinID is None: 535 skinInfo = GetSkinUsedWithTypeAndMaterial(self.skin.materialID, self.controller.typeID) 536 ownerText = 'my corp' if skinInfo.isStructureSkin else 'me' 537 m = [['GM: Give %s time-limited' % ownerText, GiveSkin, (self.skin.materialID, self.controller.typeID)], ['GM: Give %s single-use' % ownerText, GiveLimitedSkin, (self.skin.materialID, self.controller.typeID)], ['GM: Give %s permanently' % ownerText, GivePermanentSkin, (self.skin.materialID, self.controller.typeID)]] 538 m += [['GM: skinID: %s' % skinInfo.skinID, blue.pyos.SetClipboardData, (str(skinInfo.skinID),)]] 539 return m 540 else: 541 m = [['GM: Remove SKIN', RemoveSkin, (self.skin.skinID,)]] 542 m += [['GM: Set SKIN as expired (processed on next login)', GiveExpiredSkin, (self.skin.skinID,)]] 543 m += [['GM: skinID: %s' % self.skin.skinID, blue.pyos.SetClipboardData, (str(self.skin.skinID),)]] 544 return m 545 546 def OpenMarketWindow(self): 547 self.cosmeticsSvc.OpenMarketForTypeWithMaterial(self.controller.typeID, self.skin.materialID) 548 LogBuySkinIsk(self.controller.typeID, self.skin.materialID, self._logContext) 549 550 def ShowMaterialInfo(self): 551 sm.GetService('info').ShowInfo(typeSkinMaterial, itemID=self.skin.materialID) 552 553 554 class ThirdPartySkinEntry(BaseSkinEntry): 555 556 def _ConstructIcon(self): 557 super(ThirdPartySkinEntry, self)._ConstructIcon() 558 self.iconShadow.display = False 559 560 def _ConstructMain(self): 561 super(ThirdPartySkinEntry, self)._ConstructMain() 562 prefixText = eveformat.color(GetByLabel('Achievements/UI/active'), carbonui.TextColor.NORMAL) 563 expiresText = eveformat.color(GetByLabel('UI/Skins/SkinDurationPermanent'), carbonui.TextColor.SECONDARY) 564 carbonui.text.styles.TextDetail(name='durationText', parent=self.textCont, align=uiconst.TOTOP, text='%s: %s' % (prefixText, expiresText), padTop=3) 565 try: 566 creatorName = cfg.eveowners.Get(self.skin.skin_data.creator_character_id).ownerName 567 except: 568 creatorName = None 569 570 if creatorName: 571 prefixText = eveformat.color(GetByLabel('UI/Personalization/ShipSkins/SKINR/MadeBy'), carbonui.TextColor.NORMAL) 572 creatorText = eveformat.color(creatorName, carbonui.TextColor.SECONDARY) 573 carbonui.text.styles.TextDetail(name='madeBy', parent=self.textCont, align=uiconst.TOTOP, text='%s: %s' % (prefixText, creatorText), padTop=3) 574 575 576 class SkinGroupEntry(ContainerAutoSize, EntryMixin): 577 default_align = uiconst.TOTOP 578 default_state = uiconst.UI_PICKCHILDREN 579 580 def ApplyAttributes(self, attributes): 581 super(SkinGroupEntry, self).ApplyAttributes(attributes) 582 self.text = attributes.text 583 self._entries = set() 584 self.Layout() 585 586 def AddEntry(self, entry): 587 self._entries.add(entry) 588 589 def Layout(self): 590 panel = ContainerAutoSize(name='panel', parent=self, align=uiconst.TOTOP, state=uiconst.UI_NORMAL, padTop=10) 591 panel.DelegateEvents(self) 592 self.label = carbonui.text.styles.TextBody(parent=panel, align=uiconst.TOTOP, text=self.text, maxlines=2, bold=True) 593 594 def SetLabelText(self, text): 595 self.label.text = text 596 597 def _GetEntrySortKey(self, entry): 598 return entry.parent.children.index(entry) 599 600 601 class SkinDurationLabel(carbonui.text.styles.TextDetail): 602 603 def ApplyAttributes(self, attributes): 604 super(SkinDurationLabel, self).ApplyAttributes(attributes) 605 self.skin = attributes.skin 606 self.text = self._GetExpiryText() 607 if not self.skin.permanent: 608 uthread.new(self._UpdateLabelThread) 609 610 def _UpdateLabelThread(self): 611 while True: 612 blue.synchro.SleepWallclock(1000) 613 if self.destroyed: 614 break 615 self.text = self._GetExpiryText() 616 617 def _GetExpiryText(self): 618 prefixText = eveformat.color(GetByLabel('Achievements/UI/active'), carbonui.TextColor.NORMAL) 619 expiresText = eveformat.color(self.skin.GetExpiresLabel(), carbonui.TextColor.SECONDARY) 620 return '%s: %s' % (prefixText, expiresText) 621 622 623 class SkinBuyButton(Button): 624 default_iconSize = 12 625 default_density = Density.COMPACT 626 627 def ApplyAttributes(self, attributes): 628 super(SkinBuyButton, self).ApplyAttributes(attributes) 629 self.typeID = attributes.typeID 630 self.materialID = attributes.materialID 631 self.logContext = attributes.logContext 632 633 634 class SkinBuyButtonIsk(SkinBuyButton): 635 default_name = 'SkinBuyButtonIsk' 636 default_variant = ButtonVariant.PRIMARY 637 default_label = GetByLabel('UI/Skins/BuyWithIsk') 638 default_texturePath = 'res:/UI/Texture/classes/skins/isk_12.png' 639 640 def OnClick(self, *args): 641 if not self.enabled: 642 return 643 sm.GetService('cosmeticsSvc').OpenMarketForTypeWithMaterial(self.typeID, self.materialID) 644 LogBuySkinIsk(self.typeID, self.materialID, self.logContext) 645 646 647 class SkinBuyButtonPlex(SkinBuyButton): 648 default_name = 'SkinBuyButtonPlex' 649 default_style = ButtonStyle.MONETIZATION 650 default_label = GetByLabel('UI/Skins/BuyWithAur') 651 default_texturePath = PLEX_12_SOLID_WHITE 652 default_pickState = carbonui.PickState.ON 653 654 def ApplyAttributes(self, attributes): 655 super(SkinBuyButtonPlex, self).ApplyAttributes(attributes) 656 self.types = self.GetLicenseTypesForMaterial() 657 self.offers = None 658 if callable(self.types): 659 self.types = self.types() 660 if isinstance(self.types, numbers.Number): 661 self.types = [self.types] 662 if self.types is not None: 663 uthread.new(self.FindOffersAndReveal) 664 665 def FindOffersAndReveal(self): 666 try: 667 store = sm.GetService('vgsService').GetStore() 668 self.offers = store.SearchOffersByTypeIDs(self.types) 669 self.display = len(self.offers) > 0 670 except Exception as e: 671 if len(e.args) >= 1 and e.args[0] == 'tokenMissing': 672 log.LogInfo('Failed to search the NES for offers due to missing token') 673 else: 674 log.LogException('Failed to search the NES for offers') 675 676 def GetLicenseTypesForMaterial(self): 677 licenses = sm.GetService('cosmeticsSvc').static.GetLicensesForTypeWithMaterial(self.typeID, self.materialID) 678 return [ license.licenseTypeID for license in licenses ] 679 680 def OnClick(self, *args): 681 if not self.enabled: 682 return 683 sm.GetService('vgsService').OpenStore(typeIds=self.types) 684 LogBuySkinAur(self.typeID, self.materialID, self.logContext) 685 686 687 class VerticalCenteredContainer(ContainerAutoSize): 688 default_alignMode = uiconst.TOTOP 689 690 def __init__(self, **kwargs): 691 kwargs['callback'] = self._on_size_change 692 super(VerticalCenteredContainer, self).__init__(**kwargs) 693 694 @contextlib.contextmanager 695 def auto_size_disabled(self): 696 was_enabled = self.isAutoSizeEnabled 697 self.DisableAutoSize() 698 try: 699 yield self 700 finally: 701 if was_enabled: 702 self.EnableAutoSize() 703 704 def _on_size_change(self): 705 to_top_aligned_children = filter(lambda child: child.align == uiconst.TOTOP, self.children) 706 if to_top_aligned_children: 707 content_height = 0 708 for i, child in enumerate(to_top_aligned_children): 709 content_height += child.height + child.padTop + child.padBottom 710 if i > 0: 711 content_height += child.top 712 713 if content_height <= 0: 714 return 715 _, height = self.GetCurrentAbsoluteSize() 716 adjusted_top = max(0.0, math.floor((height - content_height) / 2.0)) 717 to_top_aligned_children[0].top = adjusted_top 718 719 def SetSizeAutomatically(self): 720 if self.align == uiconst.TOALL: 721 self._on_size_change() 722 else: 723 super(VerticalCenteredContainer, self).SetSizeAutomatically() 724 725 726 class OpenParagonHubSection(ContainerAutoSize): 727 default_align = carbonui.Align.TOTOP 728 default_alignMode = carbonui.Align.TOTOP 729 730 def __init__(self, shipTypeID = None, *args, **kwargs): 731 super(OpenParagonHubSection, self).__init__(*args, **kwargs) 732 self.shipTypeID = shipTypeID 733 Line(parent=self, align=carbonui.Align.TOTOP, weight=2, color=eveColor.PARAGON_BLUE, outputMode=uiconst.OUTPUT_COLOR_AND_GLOW, glowBrightness=0.3, opacity=0.6) 734 container = ContainerAutoSize(parent=self, align=carbonui.Align.TOTOP, alignMode=carbonui.Align.TOTOP, bgColor=eveColor.Color(eveColor.PARAGON_BLUE).SetOpacity(0.1).GetRGBA()) 735 Sprite(parent=container, state=carbonui.uiconst.UI_DISABLED, align=carbonui.Align.CENTERLEFT, texturePath='res:/UI/texture/classes/paintTool/paragon_logo.png', height=32, width=34, left=8) 736 carbonui.text.styles.TextBody(parent=container, align=carbonui.Align.TOTOP, padding=(50, 16, 8, 0), text=GetByLabel('UI/Personalization/ShipSkins/SKINR/Store/Title')) 737 carbonui.text.styles.TextDetail(parent=container, align=carbonui.Align.TOTOP, padding=(50, 0, 8, 16), text=GetByLabel('UI/Personalization/ShipSkins/SKINR/Store/ShortDescription')) 738 open_store_btn = Button(parent=self, align=carbonui.Align.TOTOP, label=GetByLabel('UI/Personalization/ShipSkins/SKINR/Store/ViewStoreButton'), frame_type=carbonui.ButtonFrameType.CUT_BOTTOM_LEFT_RIGHT, func=self._open_store) 739 open_store_btn.analyticID = OPEN_SKINR_BUTTON_ANALYTIC_ID 740 741 def _open_store(self, *args, **kwargs): 742 from eve.client.script.ui.cosmetics.ship.shipSKINRWindow import ShipSKINRWindow 743 from eve.client.script.ui.cosmetics.ship.const import SkinrPage 744 ShipSKINRWindow.open_on_paragon_hub(ship_type_id=self.shipTypeID)